
[타입스크립트] 맵드 타입
타입스크립트의 고급 타입인 맵드 타입(mapped type)이란 기존에 정의되어 있는 타입을 새로운 타입으로 변환해 주는 기능을 말한다.
예를 들어 인터페이스에 있는 모든 속성을 루프문 같이 순회해서 optional(?) 로 바꾸거나 readonly 로 지정할수 있으며 아예 지정된 타입을 바꿔서 변경된 타입을 반환할 수도 있다.

아직 맵드 타입에 대해 문법을 배우지는 않았지만 간단하게 살펴보자면 다음과 같다.
interface Obj {
prop1: string;
prop2: string;
}
type ChangeType<T> = {
[K in keyof T]: number;
};
type Result = ChangeType<Obj>;
/*
{
prop1: number;
prop2: number;
}
Obj 라는 인터페이스의 객체 속성 타입 string을 ChangeType<Obj> 을 통해 Obj 타입들을 number로 모두 바꿔주고 Result 타입 별칭에게 반환하였다.
그래서 결과적으로 타입 Result는 { prop1: number; prop2; number } 와 같은 객체 타입을 가지게 되었다.
자바스크립트로 따지면 객체의 속성의 value를 함수에서 for in 으로 객체를 순회해 각 속성의 값들을 문자열에서 숫자로 바꿔주는 것과 비슷해 보인다.
const Obj = {
prop1: "홍길동",
prop2: "홍길동"
}
function ChangeValue(T) {
for(let K in T) { T[K] = 1000; }
return T;
}
const Result = ChangeValue(Obj);
/*
{
prop1: 1000,
prop2: 1000
}
타입스크립트는 타입을 따로 다루는 언어이니, 위의 로직에서 value가 아니라 type으로 바꿔 생각해보면 된다.
맵드 타입 문법

맵드 타입은 객체의 속성들을 순회해서 속성의 타입을 다른 타입으로 바꿔주는 역할을 한다.
객체 타입의 속성들을 순회하기 때문에 이를 응용해 모든 객체의 속성들을 순회해서 optional(?) 로 바꾸거나 readonly 로 지정할 수도 있다.
기존에는 아래처럼 인터페이스로 일일히 따로따로 지정하던 것을,
interface PersonPartial {
name?: string;
age?: number;
}
interface PersonReadonly {
readonly name: string;
readonly age: number;
}
맵드 타입 문법을 이용해서 마치 함수를 이용하는 것처럼 속성들을 순회해서 변경해주고 그 결과값을 type alias에게 반환해 준다.
보다시피 맵드 타입은 제네릭과 결합하면 매우매우 강력해진다.
interface Person {
name: string;
age: number;
}
// 제네릭을 순회해서 readonly로 설정
type ReadOnly<T> = {
readonly [P in keyof T]: T[P];
};
// 제네릭을 순회해서 optional로 설정
type ParTial<T> = {
[P in keyof T]?: T[P];
};
type PersonPartial = Partial<Person>;
/*
type PersonPartial = {
name?: string | undefined;
age?: string | undefined;
}
*/
type ReadonlyPerson = Readonly<Person>;
/*
type ReadonlyPerson = {
readonly name: string;
readonly age: string;
}
*/
다음으로는 맵드 타입 구성 문법에 대해 알아보자.
interface Obj {
name: string;
email: string;
}
type ObjNumber = {
[P in keyof Obj]: number // 맵드 타입
};
/*
type ObjNumber = {
name: number;
email: number;
}
*/
위 코드에서 보다시피 맵드 타입은 만들어지는 반환 값이 객체 타입 형태이기 때문에 중괄호로 둘러쌓여져 있으며 중괄호 안의 대괄호는 키 부분을 나타낸다.
인터페이스의 indexable 타입을 생각하면 된다.

그런데 인터페이스의 indexable 타입과 다른점이 있는데 바로 대괄호 안에 in 키워드를 사용한다는 점이다.
in 연산자는 자바스크립트의 for in 으로 생각하면 편하다.
for in 은 객체의 key를 순회하는 루프문인데 이 역할을 똑같이 따라한다고 보면 된다.
그리고 대괄호 안의 key의 명칭은 indexable 타입이나 제네릭 같이 마음대로 지어도된다. for in 으로 따지면 for (let i in obj) 에서의 변수 i 와 같은 개념이다. (in으로 순회하는 값들을 저장하는 임시 변수)
마지막으로 keyof 는 인터페이스 Obj의 객체 타입 속성들을 뽑아 유니온으로 name | email 만들어주는 역할을 한다.
맵드 타입 활용 예제
맵드 타입으로 객체 타입 생성
유니온으로 속성명들을 지정해주고 in 키워드로 순회해 주면서 지정한 속명들의 타입을 boolean 이나 number 로 지정하고 완성된 타입을 타입 별칭에 반환하는 예제 이다.
type T1 = {
[K in "prop1" | "prop2"]: boolean
};
/*
type T1 = {
prop1: boolean;
prop2: boolean;
}
*/
type T2 = {
[K in "prop1" | "prop2"]: number
};
/*
type T2 = {
prop1: number;
prop2: number;
}
*/
type T3 = {
[K in "prop1" | "prop2"]: string
};
/*
type T3 = {
prop1: string;
prop2: string;
}
*/
맵드 타입을 제네릭과 결합해서 사용하면 마치 자바스크립트 함수 처럼 타입을 만들고 반환하는 로직을 구성 할 수 있다.
type Prop = 'prop1' | 'prop2';
type Make<T> = {
[K in Prop]: T
};
type T1 = Make<boolean>;
/*
type T1 = {
prop1: boolean;
prop2: boolean;
}
*/
type T2 = Make<number>;
/*
type T2 = {
prop1: number;
prop2: number;
}
*/
type T3 = Make<string>;
/*
type T3 = {
prop1: string;
prop2: string;
}
*/
이렇게 만들어진 타입으로 객체 변수를 선언하여 이용하면 된다.
const obj1: T1 = {
prop1: true,
prop2: false,
};
const obj2: T2 = {
prop1: 123,
prop2: 66666,
};
const obj3: T3 = {
prop1: 'hello',
prop2: 'world',
};
맵드 타입으로 객체 속성 제거
아래와 같이 사용자 프로필을 조회하여 객체를 반환하는 API 함수가 있다고 가정한다.
interface UserProfile {
username: string;
email: string;
profilePhotoUrl: string;
}
// 만약 api를 요청해 유저 프로파일을 응답하는 http 함수라고 가정한다면
function fetchUserProfile(): UserProfile {
return {
username: '홍길동',
email: 'hongildong@naver.com',
profilePhotoUrl: 'image',
};
}
const user: UserProfileUpdate = fetchUserProfile();
만일 사용자가 웹에서 프로필 정보를 수정한다면 이 프로필 객체의 정보를 수정하는 API는 UserProfileUpdate 라고 가정하자.
여기서 optional 키워드만 붙인 똑같은 형태의 인터페이스를 생성하는 이유는 기존의 UserProfile 인터페이스 타입은 강하게 타입이 정해져있기 때문에 객체의 속성을 삭제하기 위해서는 이런식을 조치를 취해 주어야 한다.
interface UserProfileUpdate {
username?: string;
email?: string;
profilePhotoUrl?: string;
}
// 사용자가 자신의 프로필 이미지를 삭제 처리를 하여, 유저 객체에서 프로파일 이미지 url 속성을 삭제해서 업데이트하는 api
function updateUserProfile(params: UserProfileUpdate) {
delete params.profilePhotoUrl;
}
updateUserProfile(user);
하지만 같은 모양의 인터페이스를 반복해서 선언하는 것을 가독성에 매우 안좋으니 피해야 한다.
interface UserProfile {
username: string;
email: string;
profilePhotoUrl: string;
}
interface UserProfileUpdate {
username?: string;
email?: string;
profilePhotoUrl?: string;
}
그래서 위의 인터페이스에서 반복되는 구조를 아래와 같은 방식으로 재활용 할 수 있다.
interface UserProfile {
username: string;
email: string;
profilePhotoUrl: string;
}
type UserProfileUpdate = {
[p in 'username' | 'email' | 'profilePhotoUrl']?: UserProfile[p];
};
/*
type UserProfileUpdate = {
username?: string | undefined;
email?: string | undefined;
profilePhotoUrl?: string | undefined;
}
*/
여기서 위 코드에 keyof 인터페이스를 적용하면 자동으로 객체 속성들을 모아 유니온 타입으로 변환해줘서 아래와 같이 줄일 수 있다.
interface UserProfile {
username: string;
email: string;
profilePhotoUrl: string;
}
type UserProfileUpdate = {
[p in keyof UserProfile]?: UserProfile[p];
};
인터페이스 타입 바꾸기
interface Person {
name: string;
age: number;
}
type MakeBoolean<T> = {
[P in keyof T]?: boolean
};
const pMap: MakeBoolean<Person> = {}; // Person 인터페이스의 타입들을 모두 boolean으로 변경하고 optional로 지정
/*
{
name?: boolean | undefined;
age?: boolean | undefined;
}
*/
pMap.name = true;
pMap.age = false;
pMap.age = undefined; // 선택 속성이기에 undefined 할당 가능
readonly / optional 붙이기
위에서 한번 소개한 맵드 타입의 대표적인 기법이다.
그런데 여기서 주의 깊게 봐야 할 점은 T[P] 부분인데, 이 키워드의 의미는 제네릭으로 받은 속성의 타입을 유지한다 라는 의미이다.
예를 들어 Partial<Person> 맵드 타입을 호출하면 제네릭 T에 Person이 들어올꺼고 맵드 타입 P에 Person의 keyof인 (name | age) 유니온 타입이 순회되어 하나씩 P에 들어오게 된다.
결국 T[P] 는 Person[name] 과 Person[age] 가 될 것이고 이는 곧 속성 자기 자신의 타입 밸류를 가리키는 것이니 자신의 속성의 타입을 복사해 그대로 반환한다는 의미이다.
interface Person {
name: string;
age: number;
}
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Partial<T> = {
[P in keyof T]?: T[P];
};
type PersonPartial = Partial<Person>;
/*
type PersonPartial = {
name?: string | undefined;
age?: number | undefined;
}
*/
type ReadonlyPerson = Readonly<Person>;
/*
type ReadonlyPerson = {
readonly name: string;
readonly age: number;
}
*/
readonly / optional 떼버리기
속성들을 순회해서 readonly 와 ? 를 붙여봤으면 이번엔 제거해보는 맵드 타입을 만들어 보자.
여기서 새로운 연산자가 나오는데 바로 마이너스 - 연산자이다. 어렵게 이해할 필요없이 제거의 의미를 내포한다.
interface Person1 {
name?: string;
age?: number;
}
interface Person2 {
readonly name: string;
readonly age: number;
}
type Exclude_ReadOnly<T> = {
// -readonly 라는 뜻은 readonly를 떼라 라는 의미이다.
// 반대로 +readonly 는 readonly를 추가하라는 의미인데 정수를 +1로 안쓰듯이 그냥 생략 가능함
-readonly [P in keyof T]: T[P];
};
type Exclude_ParTial<T> = {
// -? 라는 뜻은 ?를 떼라 라는 의미이다.
// 반대로 +? 는 ?를 추가하라는 의미인데 정수를 +1로 안쓰듯이 그냥 생략 가능함
[P in keyof T]-?: T[P];
};
type PersonPartial = Exclude_ParTial<Person1>;
/*
type PersonPartial = {
name: string;
age: number;
}
*/
type ReadonlyPerson = Exclude_ReadOnly<Person2>;
/*
type ReadonlyPerson = {
name: string;
age: number;
}
*/
중첩 객체 속성 만들기
맵드 타입을 이용해 중첩 객체 속성 타입을 이루는 예제이다.
/* eslint-disable */
export {};
interface Person {
name: string;
age: number;
language: string;
}
type Recorded<K extends string, T> = { [P in K]: T };
type T1 = Recorded<'p1' | 'p2', Person>;
const t: T1 = {
p1: {
name: '홍길동',
age: 88,
language: 'kor',
},
p2: {
name: '링컨',
age: 34,
language: 'eng',
},
};
맵드 타입에 새 멤버 추가
멤버를 추가하길 원한다면 교차 타입을 이용해 구성할 수 있다
interface Person {
name: string;
age: number;
}
type PartialWithNewMember<T> = {
[P in keyof T]?: T[P];
} & { newMember: boolean };
type PersonNew = PartialWithNewMember<Person>;
/*
type PersonNew = {
name?: string | undefined;
age?: number | undefined;
newMember: boolean;
}
*/
const person2: PersonNew = {
name: '홍길동',
newMember: true,
};
Enum 타입 가드 하기
맵드 타입을 이용하면 Enum 타입의 활용도를 높일 수 있다.
enum Fruit {
Apple,
Banana,
Orange,
}
const FRUIT_PRICE: { [key in Fruit]: number } = {
[Fruit.Apple]: 1000,
[Fruit.Banana]: 1500,
[Fruit.Orange]: 2000,
};
위의 코드와 같이 Fruit 라는 enum 타입이 있는데 FRUIT_PRICE 라는 상수에서 모든 과일 가격을 관리한다고 가정을 해보면, 만약 enum 타입이 변화가 있을때 다음과 같이 빨간줄로 경고를 해준다.


이처럼 코드 수정이 필요하면 맵드 타입에 의해서 빨간줄이 나와 실수를 줄일 수 있다.
'Frontend' 카테고리의 다른 글
[Nginx] Nginx란? (0) | 2025.04.07 |
---|---|
타입스크립트 - Conditional Types (1) | 2025.04.02 |
타입스크립트 - Utility Types (0) | 2025.04.02 |
도커(Docker) (8) | 2025.03.27 |
Submodule 구축 (0) | 2025.03.11 |