Type Challenges

Readonly 2

#TypeScript

8 - Readonly 2

T에서 K 프로퍼티만 읽기 전용으로 설정해 새로운 오브젝트 타입을 만드는 제네릭 MyReadonly2<T, K>를 구현하세요. K가 주어지지 않으면 단순히 Readonly<T>처럼 모든 프로퍼티를 읽기 전용으로 설정해야 합니다.

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

풀이

type MyReadonly2<T, K extends keyof T = keyof T> = {
  [P in keyof T as P extends K ? never : P]: T[P];
} & {
  readonly [P in keyof T as P extends K ? P : never]: T[P];
};

먼저 문제에서 // @ts-expect-error 주석 부분이 있다.

// @ts-expect-error
type error = MyReadonly2<Todo1, "title" | "invalid">;

즉, 잘못된 키가 설정되는 것을 막아야하므로, K를 K extends keyof T로 설정한다.
그런데 문제에서 "K가 주어지지 않으면 단순히 Readonly<T>처럼 모든 프로퍼티를 읽기 전용으로 설정해야 합니다."라는 조건이 있기 때문에 K가 주어지지 않은 경우에 들어갈 기본값 역시 지정해주어야한다.
K가 주어지지 않으면 모든 key를 readonly로 만들기 때문에 기본값을 T의 모든 key인 keyof T로 지정해준다.

{
  [P in keyof T as P extends K ? never : P]: T[P]
} & {
  readonly [P in keyof T as P extends K ? P : never]: T[P]
}

그 다음에는 Omit에서 사용했던 방식처럼 프로퍼티 Key를 as를 통해 다시 매핑하여 필터링하는데
만약 P가 K에 포함되지 않았다면 readonly를 붙여주지 않고, P가 K에 포함된다면 readonly를 붙여준다.
이 때 두 객체 타입을 &을 통해 합쳐주어 하나의 타입으로 기능하게 만든다.

Reference

Type Challenges, 8 - Readonly 2