[TypeScript] Mapped Types

Sang Young Ha·2022년 9월 13일
post-thumbnail
  • 타입스크립트에는 Partial, Required, Readonly, 및 Pick 과 같은 utility types 가 존재한다.
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];
};
  • 회원정보를 등록하는 예시를 생각해 보자. 우리는 다음과 같은 User type 을 define 할 수 있다:
type User = {
	name: string;
    password: string;
    address: string;
    phone: string;
};
  • 회원정보는 회원이 변경이 가능하므로 다음과 같이 변경 가능한 User Type 을 define 한다:
type UserPartial = {
  name?: string; 
  password?: string; 
  address?: string; 
  phone?: string; 
};
  • 사용자 정보를 읽는데 있어, 읽기 전용 타입 또한 필요하다.
type ReadonlyUser = {
  readonly name: string;
  readonly password: string;
  readonly address: string;
  readonly phone: string;
};

-위의 세가지 User related types 를 define 하면서, 코드가 많이 중복 된다는 것을 알수가 있다. 이를 해결 하기 위해 다음과 같이 Mapped types 를 사용 할 수 있다.

{[P in K] :T} 
  • readonly 및 (?) 와 같은 modifier 들을 다음과 같이 추가 할수도 있다. +,- 사인을 통해 해당 modifier 을 추가 및 제거가 가능한데, 명시가 따로 되어 있지 않다면 default 는 + 이다.
{ readonly [P in K]? :T}
  • common mapped types syntax.
{ [ 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 }
  • Some examples
type Temp = {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"]: Temp[P] };
// { a: string, b: number }

type T4 = { [P in keyof Temp]: Temp[P] };
// {a: string, b: number, c: boolean }
  • 이제 mapped types 를 이용하여 UserPartial 을 define 해보자
type MyPartial<T> = {
	[P in keyof T]?: T[P];
};

type UserPartial = MyPartial<User>;
  • MyPartial 로 의 자리에 User type 이 들어가게 되고, keyof operator 을 통해 User type 안의 key 값들을 P 가 iterate 하며 각각 해당 key 값을 가지게 되며, User[P] 로 해당 key 에 상응 하는 type 을 가져오게 된다.
  • "as" 를 사용해서 mapped types 내의 keys 를 remapping 할 수 있다.
type MappedTypeWithNewKeys<T> = {
  [K in keyof T as NewKeyType]: T[K]
}
  • 여기서 NewKeyType 은 반드시 string | number | symbol 의 union type 의 subtype 이어야 한다. 이를 이용해, 다음과 같이 Getters utility 타입 키를 remapping 하는것이 가능 해 진다.
    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;
    // }
  • 위의 예시 코드에서 keyof T 의 return type 이 symbol 을 포함 할 가능성이 있고, Capitalize utility type 은 string type 만 process 할 수 있으므로 <string & K> 를 통해 string 이 아닌 type 들을 filter 해 준다.
  • Remapping 과 비슷하게, 특정 key 를 Exclude utility type 을 이용하여 filter 할수도 있다.
// Exclude 를 이용한 "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;
//   };
  
//Exclude utility type 은 다음과 같이 생겼다
 type Exclude<T, U> = T extends U ? never : T;

0개의 댓글