프론트엔드/TypeScript

[TypeScript] 타입스크립트 맵드 타입 (Mapped Types) 1

zero2-pooh 2023. 7. 31. 17:07

Mapped Types (맵드 타입)이란?

- 타입스크립트의 고급 타입인 맵드 타입이란 기존에 정의되어 있는 타입을 새로운 타입으로 변환해 주는 문법을 의미 한다. 

- 마치 자바스크립트 map() API 함수를 타입에 적용한 것과 같은 효과를 가진다.


맵드 타입의 기본 문법

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

이처럼 맵드 타입은 객체의 속성들을 순회해서 속성의 타입을 다른 타입으로 바꿔주는 역할을 한다.

객체 타입의 속성들을 순회하기 때문에 이를 응용해서, 모든 객체의 속성들을 순회해서 optional(?)로 바꾸거나 readonly로 지정할 수도 있다.


👇🏻기존에는 아래와 같이 인터페이스로 하나하나 따로 지정하던걸,

interface AnimalPartial {
   name?: string;
   age?: number;
}

interface AnimalReadonly {
   readonly name: string;
   readonly age: number;
}

 

 

맵드 타입 문법을 이용해서 마치 함수를 사용하는 것처럼 속성들을 순회해서 변경해 주고

➡️ 그 결과값을 type alias에게 변환해 준다.

interface Animal {
   name: string;
   age: number;
}

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

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

type AnimalPartial = Partial<Animal>;

type ReadonlyAnimal = Readonly<Animal>;
💡맵드 타입은 제네릭과 결합하면 매우 강력해진다!!

맵드 타입 기본 예제

간단하게 동물 타입을 예시로 들어 보겠다. 

type Animals = "Dog" | "Cat" | "Lion";

 

 

여기서 이 동물들의 이름에 각각 나이까지 붙인 객체를 만들고 싶다고 한다면 👇🏻아래와 같이 변환할 수 있다.

type AnimalProfiles = { [K in Animals]: number };
const animalInfo: AnimalProfiles = {
  Dog: 3,
  Cat: 5,
  Lion: 6,
}

 

 

위 코드에서 [K in Animals] 부분은 마치 자바스크립트의 for in 문법과 유사하게 동작한다.

앞에서 정의한 Animals 타입의 3개의 문자열을 각각 순회하여 number 타입을 값으로 가지는 객체의 키로 정의가 된다. 👇🏻

{ Dog: number } // 첫번째 순회 
{ Cat: number } // 두번째 순회 
{ Lion: number } // 세번째 순회

 

그래서 이 타입들이 아래와 같이 정의가 된다!

type HeroProfiles = {
  Hulk: number;
  Thor: number;
  Capt: number;
}

맵드 타입 실용 예제 1

위에서 살펴본 코드는 예제 코드로 문법과 동작을 이해하기 위해 간단한 코드이다.

 

실제로 서비스를 개발할 때는 위와 같은 코드보다는 아래와 같은 코드를 더 많이 사용하게 된다고 한다.

 

type Subset<T> = {
  [K in keyof T]?: T[K];
}

위 코드는 키와 값이 있는 객체를 정의하는 타입을 받아 그 객체의 부분 집합을 만족하는 타입으로 변환해 주는 문법이다.

 

 

예를 들면 만약 👇🏻아래와 같은 인터페이스가 있다고 할 때 

interface Animal {
  age: number;
  name: string;
}

 

 Subset 타입을 적용하면 아래와 같은 객체를 모두 정의할 수 있다.

const ageOnly: Subset<Animal> = { age: 2 };
const nameOnly: Subset<Animal> = { name: 'Tom' };
const ironman: Subset<Animal> = { age: 2, name: 'Tom' };
const empty: Subset<Animal> = {};

 

맵드 타입 실용 예제 2

아래와 같이 사용자 프로필을 조회하는 API 함수가 있다고 했을 때 

interface UserProfile {
  username: string;
  email: string;
  profilePhotoUrl: string;
}

function fetchUserProfile(): UserProfile {
  // ...
}

이 프로필의 정보를 수정하는 API는 아마 👇🏻아래와 같은 형태일 것이다.

 

interface UserProfileUpdate {
  username?: string;
  email?: string;
  profilePhotoUrl?: string;
}

function updateUserProfile(params: UserProfileUpdate) {
  // ...
}

 

이때 👇🏻아래와 같이 동일한 타입에 대해서 반복해서 선언하는 것을 피해야 한다.

interface UserProfile {
  username: string;
  email: string;
  profilePhotoUrl: string;
}

interface UserProfileUpdate {
  username?: string;
  email?: string;
  profilePhotoUrl?: string;
}

위의 인터페이스에서 반복되는 구조를 아래와 같은 방식으로 재활용할 수 있다.

type UserProfileUpdate = {
  username?: UserProfile['username'];
  email?: UserProfile['email'];
  profilePhotoUrl?: UserProfile['profilePhotoUrl'];
}

혹은 좀 더 줄여서 아래와 같이 정의할 수도 있다.

type UserProfileUpdate = {
  [p in 'username' | 'email' | 'profilePhotoUrl']?: UserProfile[p]
}

여기서 위 코드에 keyof를 적용하면 아래와 같이 줄일 수 있다.

type UserProfileUpdate = {
  [p in keyof UserProfile]?: UserProfile[p]
}

맵드 타입에 대해서는 다음에 2부로 포스팅을 하려고 한다. 

객체 제거 부터해서.. 등등 너무 많음.. 

일단 1부는 여기까지 하는 걸로🖐🏻

 

 

 

 

 

참고

https://joshua1988.github.io/ts/usage/mapped-type.html#%EB%A7%B5%EB%93%9C-%ED%83%80%EC%9E%85-mapped-type-%EC%9D%B4%EB%9E%80

https://inpa.tistory.com/entry/TS-📘-타입스크립트-Mapped-types-완벽-이해하기#readonly_/_optional_붙이기

https://typescript-kr.github.io/pages/advanced-types.html#매핑-타입의-추론-inference-from-mapped-types