-
맵드 타입(Mapped Type)인사이트 2025. 4. 3. 14:13
맵드 타입(Mapped Type) 정의: 타입 연산은 기존 타입을 기반으로 새로운 타입을 생성.
✅ 기본 문법
{ [ P in K ] : T } { [ P in K ] ? : T } { readonly [ P in K ] : T } { readonly [ P in K ] ? : T }
✅ 여러 개의 키-값 쌍을 가지는 객체를 표현할 때
type MagicWords = '이걸요?' | '제가요?' | '왜요?'; type Gauges = { [K in MagicWords]: number }; const gaugesInfo: Gauges = { "이걸요?": 54, "제가요?": 1000, "왜요?": 33, }
✅ 불변 데이터의 유연한 변환 가능 공식문서
- 매핑중에는 추가할 수 있는 수정자로 readonly와 ? 있다. 각각 가변성과 선택성에 영향을 미침.
- + 또는 -를 접두사로 붙여서 이런 수정자를 추가하거나 제거할 수 있다.(접두사를 추가 하지 않으면 +로 간주)
type CreateMutable<Type> = { -readonly [Property in keyof Type]: Type[Property]; }; type LockedAccount = { readonly id: string; readonly name: string; }; type UnlockedAccount = CreateMutable<LockedAccount>;
✅ 같은 인터페이스 구조에서 옵셔널로 사용할 때(반복 되는 구조를 재활용)
// 컴포넌트에서 옵셔널 하지만, props 누락을 방지하게 위해 에러가 나게 하고싶었던 것 같음.. interface Item { subdomain: string | undefined; description: string | undefined; phone: string | undefined; time: string | undefined; link: string | undefined; } // 개선 1. 필수 타입, 옵셔널 타입 분리 → 같은 것들이 너무 많이 증식함. 일일히 다써야해서 코드량도 불필요하게 많아짐 interface Item { subdomain: string; description: string; phone: string; time: string; link: string; } interface ItemOptional { subdomain?: string; description?: string; phone?: string; time?: string; link?: string; } // 개선 2. 기존 root interface는 유지하되, mapped타입으로 개선 interface Item { subdomain: string; description: string; phone: string; time: string; link: string; } type Mapped = { [K in keyof Item]?: Item[K]; };
✅ 인터페이스 타입 바꾸기
interface Person { name: string; age: number; } type MakeBoolean<T> = { [P in keyof T]?: boolean }; const test: MakeBoolean<Person> ={ name: undefined, age: true, }
💡 번외, 실제 질문 받았던 사례) 이러한 연산 시스템은 왜 interface에서는 사용할 수 없을까? → interface의 선언 병합 시스템 때문.
✅ Interface, type alias 특성의 차이
- 인터페이스(interface)와 타입 별칭(type alias)은 객체의 형태를 정의하는 데 사용.
- 타입 별칭의 타입 연산의 범위는 ? → 유니온 타입, 교차 타입, 조건부 타입, 맵드 타입 등 다양한 타입 연산을 지원한다.
- 인터페이스는 주로 객체의 형태를 정의하는데 사용되며, 타입 연산보다는 선언 병합(declaration merging, 인터페이스는 동일한 이름으로 여러 번 선언 및 병합)과 같은 특정 기능에 초점을 맞춘다.
[가상 시나리오]
interface Conflicting { prop1: string; } interface Conflicting { [K in keyof Conflicting]: number; // Mapped Types 사용 (원래 허용되지 않음, 가상의 상황) } interface Conflicting { prop2: boolean; } const conflictingObj: Conflicting = { prop1: 10, // 오류: string 타입이 필요 prop2: true, };
[분석]
1. 우선 순위
- TypeScript는 인터페이스 속성의 타입을 결정할 때 초기 선언을 기준으로 한다.
- 초기 선언에서 prop1은 string 타입으로 명시되었으므로, 이후의 모든 선언과 할당은 이 타입을 준수해야 한다.
2. 가상 Mapped Types 적용 및 타입 충돌
가상 시나리오에서 Mapped Types를 사용하여 prop1의 타입을 number로 변경하려고 시도한다.
하지만 초기 선언에서 prop1은 이미 string 타입으로 정의되었으므로, number 타입을 할당하는 것은 타입 충돌을 일으킨다.
3. 결론
- prop1이 초기 선언에서 이미 string 타입으로 정의되었기 때문에, 이후에 number 타입을 할당하는 것은 타입 오류를 일으킨다.
[올바른 접근 방법]
interface Original { prop1: string; prop2: number; } type MappedType = { [K in keyof Original]: boolean; }; const obj: MappedType = { prop1: true, prop2: false, };
- Original Interface의 속성을 기반으로 새로운 타입을 생성한다.
- [K in keyof Original]: boolean; → 각 속성(K)의 타입을 boolean으로 변환.
- MappedType은 { prop1: boolean; prop2: boolean; }과 동일한 타입이 된다.
'인사이트' 카테고리의 다른 글
타입스크립트 컴파일러 (1) 2025.04.17 인덱스드 시그니처 (Index Signatures)와 인덱스드 접근 타입 (Indexed Access Types) (0) 2025.03.27 타입스크립트의 any는 언제 써야할까? 이를 보완할 수 있는 방법은 없을까? (1) 2025.03.06 프론트엔드라도 수학이 필요하다. (feat: 친동생과의 수학과외 시작) (0) 2025.02.22 조화로운 UI/UX: 우에다 쇼지의 균형에서 배우다 (0) 2025.02.01