ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 맵드 타입(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; }과 동일한 타입이 된다.

     

Designed by Tistory.