5. 맵드 타입(Mapped Type)
1. 맵드 타입이란?
- 맵드 타입이란 기존에 정의되어 있는 타입을 새로운 타입으로 변환해 주는 문법을 의미한다.
- 마치 자바스크립트의
map
API 함수를 타입에 적용한 것과 같은 효과를 가진다.
2. 자바스크립트의 map
함수란?
- 자바스크립트의
map
API는 배열을 다룰 때 유용한 자바스크립트 내장 API이다.
var arr = [
{ id: 1, title: "함수" },
{ id: 2, title: "변수" },
{ id: 3, title: "인자" },
];
var result = arr.map(function (item) {
return item.title;
});
console.log(result); // ['함수', '변수', '인자'];
- 위 코드는 세 개의 객체를 요소로 가진 배열
arr
에 map
API를 적용한 코드이다.
3. 맵드 타입의 기본 문법
- 맵드 타입은 위에서 살펴본 자바스크립트의
map
함수를 타입에 적용한 것과 같은 효과를 가진다.
- 이를 위해서는 다음과 같은 형태의 문법을 사용해야 한다.
{ [ P in K ] : T }
{ [ P in K ] ? : T }
{ readonly [ P in K ] : T }
{ readonly [ P in K ] ? : T }
4. 맵드 타입 기본 예제
- 맵드 타입의 가장 간단한 예제를 보자.
- 다음과 같이 헐크, 토르, 캡틴을 유니언 타입으로 묶어주는
Heroes
라는 타입이 있다.
type Heroes = "Hulk" | "Thor" | "Capt";
- 여기서 이 세 영웅의 이름에 각각 나이까지 붙인 객체를 만들 경우 다음과 같이 변환할 수 있다.
type Heroes = "Hulk" | "Thor" | "Capt";
type HeroProfiles = { [K in Heroes]: number };
const heroInfo: HeroProfiles = {
Hulk: 54,
Thor: 1000,
Capt: 33,
};
- 위 코드에서
[K in Heroes]
부분은 마치 자바스크립트의 for in
문법과 유사하게 동작한다.
- 앞에서 정의한
Heroes
타입의 세 개의 문자열을 각각 순회하여 number
타입을 값으로 가지는 객체의 키로 정의가 된다.
{
Hulk: number;
} // 첫번째 순회
{
Thor: number;
} // 두번째 순회
{
Capt: number;
} // 세번째 순회
- 따라서 위의 원리가 적용된
HeroProfiles
의 타입은 다음과 같이 정의된다.
type HeroProfiles = {
Hulk: number;
Thor: number;
Capt: number;
};
5. 맵드 타입 실용 예제 1
- 앞에서 살펴본 예제는 맵드 타입의 문법과 동작을 이해하기 위해 간단한 코드를 사용했다.
- 실제로 서비스를 개발할 때는 위와 같은 코드보다는 다음과 같은 코드를 더 많이 사용하게 된다.
type Subset<T> = {
[K in keyof T]?: T[K];
};
- 위 코드는 키와 값이 있는 객체를 정의하는 타입을 받아 그 객체의 부분 집합을 만족하는 타입으로 변환해 주는 문법이다.
- 만약 다음과 같은 인터페이스가 있다고 할 때 위
Subset
타입을 적용하면 다음의 객체를 모두 정의할 수 있다.
type Subset<T> = {
[K in keyof T]?: T[K];
};
interface Person {
age: number;
name: string;
}
const ageOnly: Subset<Person> = { age: 23 };
const nameOnly: Subset<Person> = { name: "Tony" };
const ironman: Subset<Person> = { age: 23, name: "Tony" };
const empty: Subset<Person> = {};
6. 맵드 타입 실용 예제 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];
};
References