Published on

Mapped Types

Mapped Types

Typescript의 Partial, Required, Readonly and Pick 유틸리티 타입은 내부적으로 어떻게 구현되어 있을까?

type Partial<T> = {
  [P in keyof T]?: T[P];
};

type Required<T> = {
  [P in keyof T]-?: T[P];
};

type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

위와 같이 original object type을 new object type으로 매핑 시킬 수 있는 generic type을 mapped type이라고 부른다.

mapped type의 syntax는 다음과 같다.

mapping process에서 read-only, optional(?) modifier를 정의해줄 수 있고, plus(+)와 minus(-) prefix를 붙여서 modifier를 추가 제거한다.
+, - prefix가 생략되면 default는 plus다.

{ [ P in K ] : T }
{ [ P in K ] ?: T }
{ [ P in K ] -?: T }
{ readonly [ P in K ] : T }
{ readonly [ P in K ] ?: T }
{ -readonly [ P in K ] ?: T }

다음의 mapped type 예제를 참고한다.

type Item = { a: string; b: number; c: boolean };

type T1 = {
  [P in 'x' | 'y']: number;
};
// { x: number, y: number }

type T2 = {
  [P in 'x' | 'y']: P;
};
// { x: 'x', y: 'y' }

type T3 = {
  [P in 'a' | 'b']: Item[P];
};
// { a: string, b: number }

type T4 = {
  [P in keyof Item]: Item[P];
};
// { a: string, b: number, c: boolean }

Mapped Types의 key 변경하기

Typescript 4.1 부터 as 를 사용하여 mapped type의 key를 변경 할 수 있는 방법을 제공한다.

아래 예제에서 Capitalize는 첫 번째 글자를 대문자로 변경한다.
<string & K> 에서 앞의 string의 의미는 non-string type의 key를 필터링해주기 위함이다.

type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface Person {
  name: string;
  age: number;
  location: string;
}
type LazyPerson = Getters<Person>;
// {
//   getName: () => string;
//   getAge: () => number;
//   getLocation: () => string;
// }

아래 예제에서는 kind property를 제거한다.

// Remove the 'kind' property
type RemoveKindField<T> = {
  [K in keyof T as Exclude<K, 'kind'>]: T[K];
};
interface Circle {
  kind: 'circle';
  radius: number;
}
type KindlessCircle = RemoveKindField<Circle>;
//   type KindlessCircle = {
//       radius: number;
//   };

참조