Partial<T>는 타입 T의 모든 프로퍼티를 선택적으로 만듭니다. 이 유틸리티 타입이 내부적으로 어떻게 동작하는지 들여다보면, 맵드 타입(Mapped Types)을 만나게 됩니다. 맵드 타입은 기존 타입의 프로퍼티를 하나씩 순회하며 변환하는 기능입니다.
[K in keyof T] 문법으로 타입 T의 모든 키를 순회합니다.
type ReadonlyVersion<T> = {
readonly [K in keyof T]: T[K];
};
interface User {
name: string;
age: number;
}
type ReadonlyUser = ReadonlyVersion<User>;
// { readonly name: string; readonly age: number }
K: 현재 순회 중인 키keyof T: T의 모든 키의 유니온T[K]: 해당 키의 원래 값 타입프로퍼티에 readonly와 ?(선택적)를 추가하거나 -로 제거할 수 있습니다.
// 모든 프로퍼티를 선택적으로 (Partial 구현)
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
// 모든 선택적 프로퍼티를 필수로 (Required 구현)
type MyRequired<T> = {
[K in keyof T]-?: T[K];
};
// 모든 프로퍼티를 readonly로 (Readonly 구현)
type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};
// readonly 제거
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
-?는 선택적 수정자를 제거하고, -readonly는 읽기 전용 수정자를 제거합니다.
키는 유지하고 값의 타입만 바꿀 수 있습니다.
// 모든 값을 string으로
type Stringify<T> = {
[K in keyof T]: string;
};
// 모든 값을 nullable로
type Nullable<T> = {
[K in keyof T]: T[K] | null;
};
interface User {
id: number;
name: string;
}
type NullableUser = Nullable<User>;
// { id: number | null; name: string | null }
as 절로 키 이름을 변환할 수 있습니다.
// 모든 키에 "get" 접두사 붙이기
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface User {
name: string;
age: number;
}
type UserGetters = Getters<User>;
// { getName: () => string; getAge: () => number }
Capitalize: 문자열의 첫 글자를 대문자로 만드는 내장 유틸리티 타입. Capitalize<"name">은 "Name"이 됩니다.
as never로 특정 키를 제거할 수 있습니다.
// string 타입 프로퍼티만 남기기
type OnlyStrings<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};
interface Mixed {
name: string;
age: number;
email: string;
isActive: boolean;
}
type StringFields = OnlyStrings<Mixed>;
// { name: string; email: string }
interface DomEvents {
click: MouseEvent;
keydown: KeyboardEvent;
scroll: Event;
}
type EventHandlers = {
[K in keyof DomEvents as `on${Capitalize<K>}`]: (event: DomEvents[K]) => void;
};
// {
// onClick: (event: MouseEvent) => void;
// onKeydown: (event: KeyboardEvent) => void;
// onScroll: (event: Event) => void;
// }
맵드 타입이 어떻게 쓰이는지 확인하는 가장 좋은 방법은 내장 유틸리티 타입의 소스를 보는 것입니다.
// TypeScript 내장 구현 (lib.es5.d.ts)
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];
};
type Record<K extends keyof any, T> = {
[P in K]: T;
};
맵드 타입은 TypeScript 타입 시스템의 강력한 변환 도구입니다. 반복되는 타입 정의를 줄이고, 기존 타입과 파생 타입 사이의 동기화를 자동으로 유지할 수 있습니다.