[TypeScript] 타입스크립트 맵드 타입 (Mapped Types) 1
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://inpa.tistory.com/entry/TS-📘-타입스크립트-Mapped-types-완벽-이해하기#readonly_/_optional_붙이기
https://typescript-kr.github.io/pages/advanced-types.html#매핑-타입의-추론-inference-from-mapped-types