Readonly 2

Challenge

Implement a generic MyReadonly2<T, K> which takes two type argument T and K.

K specify the set of properties of T that should set to Readonly. When K is not provided, it should make all properties readonly just like the normal Readonly<T>.

For example

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

const todo: MyReadonly2<Todo, "title" | "description"> = {
  title: "Hey",
  description: "foobar",
  completed: false,
};

todo.title = "Hello"; // Error: cannot reassign a readonly property
todo.description = "barFoo"; // Error: cannot reassign a readonly property
todo.completed = true; // OK

Solution

Auch zur Lösung dieses Problems benötigen wir wieder einen Mapped Typ. Als Konditionen für die Eingabewerte definieren wir einmal ein Objekt sowie Attribute des Objektes.

// T muss ein Objekt sein, K muss ein Attribut oder eine Vereinigung von Attributen aus T sein.
type MyReadonly2<T extends Record<string, any>, K extends keyof T>

In dieser Form des Typs ist die Eingabe von zwei Typen Pflicht. Da es aber in der Aufgabe heißt, man solle auch auf das zweite Argument verzichten können, ist ein Default-Wert notwendig. Dieser wird durch die Zuweisung direkt beim Eingabetyp definiert, also:

// K extends keyof T = keyof T bedeutet, dass, falls das zweite Argument fehlt, einfach alle Attribute von T als Wert für diesen Parameter genutzt werden
type MyReadonly2<T extends Record<string, any>, K extends keyof T = keyof T>

Nun können wir den finalen Typ definieren, indem wir die übergebenen Attribute auf readonly setzen und die beiden Objekttypen anschließend vereinen.

type MyReadonly2<T, K extends keyof T = keyof T> = {
  readonly [Key in K]: T[Key];
} & Omit<T, K>;

References