본문 바로가기
C.W.K.
Stream
Lesson 04 of 06 · published

매핑된 타입: 모든 키에 같은 변환 적용하기

~11 min · type-manipulation, mapped-types, type-transformation

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete
"객체 타입의 키를 하나씩 순회하며 같은 규칙으로 새 모양을 만든다."

타입 수준의 반복문

{ [K in keyof T]: T[K] }T의 모든 키를 차례로 가져와 같은 키와 값 타입으로 새 객체 타입을 만든다. 그대로 쓰면 원본과 같은 모양이지만, 프로퍼티 표시에 규칙을 더하면 유용한 변환이 돼. 예를 들어 { [K in keyof T]?: T[K] }가 바로 Partial<T>의 핵심 원리야.

여기서 K는 반복 중인 현재 키이고 T[K]는 그 키에 대응하는 값 타입이야. 값 타입을 전부 boolean으로 바꾸거나, 조건부 타입으로 특정 프로퍼티만 감싸는 식으로 변형할 수 있어. 중요한 건 키와 값의 관계를 잃지 않은 채 전체 객체에 일관된 규칙을 적용한다는 점이지.

선택성과 읽기 전용을 더하고 빼기

?readonly 수정자는 매핑 과정에서 조절할 수 있어. +?는 선택성을 더하고 -?는 없애며, +readonly-readonly도 같은 방식으로 작동해. 더하기 기호는 기본값이라 흔히 생략하지만, 빼기 기호는 기존 속성을 제거할 때 꼭 필요해.

type MutableRequired<T> = {
  -readonly [K in keyof T]-?: T[K];
};

이 타입은 모든 프로퍼티에서 readonly와 선택성을 함께 제거해. 다만 타입에서 읽기 전용 표시를 지웠다고 이미 얼어 있는 런타임 객체가 바뀌는 것은 아니야. 정적 계약과 실제 객체 동작을 구분해야 해.

as로 키 이름까지 바꾸기

{ [K in keyof T as NewKey]: ... } 형태를 쓰면 값을 바꾸는 데서 그치지 않고 키를 다시 만들 수 있어. 문자열 키 앞에 get을 붙여 getter 모음을 만들거나, 조건에 맞지 않는 키를 never로 보내 제외하는 패턴이 대표적이야.

키 재매핑은 강력하지만 지나치면 타입 오류가 암호문처럼 변해. 도메인에서 반복되는 실제 규칙을 표현할 때 쓰고, 한 번만 등장하는 작은 객체를 멋져 보이게 만들려고 쓰지는 마. 읽는 사람이 변환 전후를 머릿속에서 복원할 수 있어야 좋은 타입이야.

매핑 타입의 as 키 재매핑은 TypeScript 4.1에서 추가됐어. 템플릿 리터럴 타입과 함께 쓰면 각 키에 접두사를 붙이거나 조건에 맞지 않는 키를 never로 보내 제외할 수 있어.

매핑된 타입은 복사기가 아니라 관계 보존 변환기야. 키 집합을 원본에서 가져오고, 각 키에 적용할 규칙만 한 곳에 적어 중복과 불일치를 함께 줄여.

Code

Mapped 타입 — 모든 utility 가 이 패턴들로 빌드·typescript
// Identity — 문법 이해에 유용.
type Identity<T> = { [K in keyof T]: T[K] };

// Partial — 모든 key 에 `?` 추가.
type MyPartial<T> = { [K in keyof T]?: T[K] };

// Required — 모든 key 에서 `?` strip.
type MyRequired<T> = { [K in keyof T]-?: T[K] };

// Readonly — 모든 key 에 `readonly` 추가.
type MyReadonly<T> = { readonly [K in keyof T]: T[K] };

// 모든 property null-or-original 만들기.
type Nullable<T> = { [K in keyof T]: T[K] | null };

// `as` 통한 key remapping — 'get' prefix 추가.
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

type UserGetters = Getters<{ name: string; age: number }>;
// { getName: () => string; getAge: () => number }

External links

Exercise

T의 모든 프로퍼티를 string으로 바꾸는 매핑 타입 Stringify<T>를 작성해. 이어서 모든 값 타입을 Promise<T[K]>로 감싸는 Promiseify<T>도 만들고 두 결과가 예상한 모양인지 확인해.
Hint
매핑 구조는 같고 콜론 오른쪽의 값 타입만 목적에 맞게 바뀌어.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.