icon안동민 개발노트

매핑된 타입


 매핑된 타입은 기존 타입을 바탕으로 새로운 타입을 생성하는 타입스크립트의 강력한 기능입니다.

 이를 통해 타입을 동적으로 변환하고 재구성할 수 있습니다.

매핑된 타입의 기본 개념과 문법

 매핑된 타입의 기본 문법은 다음과 같습니다.

type MappedType<T> = {
    [K in keyof T]: T[K]
}

 이는 타입 T의 모든 속성을 순회하면서 새로운 타입을 만듭니다.

 예시

type Readonly<T> = {
    readonly [K in keyof T]: T[K]
};
 
interface Person {
    name: string;
    age: number;
}
 
type ReadonlyPerson = Readonly<Person>;
// { readonly name: string; readonly age: number; }

keyof 연산자와 매핑된 타입

 keyof 연산자는 객체 타입의 모든 키를 유니온 타입으로 추출합니다.

 매핑된 타입과 함께 사용하면 강력한 타입 변환이 가능합니다.

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 절을 사용한 키 재매핑

 as 절을 사용하면 매핑 과정에서 속성 키를 변환할 수 있습니다.

type Getters<T> = {
    [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};
 
interface Person {
    name: string;
    age: number;
}
 
type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number; }

내장 매핑 타입

 타입스크립트는 여러 유용한 내장 매핑 타입을 제공합니다.

type Partial<T> = { [K in keyof T]?: T[K] };
type Required<T> = { [K in keyof T]-?: T[K] };
type Readonly<T> = { readonly [K in keyof T]: T[K] };
type Pick<T, K extends keyof T> = { [P in K]: T[P] };
type Record<K extends keyof any, T> = { [P in K]: T };

 사용 예시

interface Todo {
    title: string;
    description: string;
    completed: boolean;
}
 
type PartialTodo = Partial<Todo>;
// { title?: string; description?: string; completed?: boolean; }
 
type RequiredTodo = Required<Todo>;
// { title: string; description: string; completed: boolean; }
 
type ReadonlyTodo = Readonly<Todo>;
// { readonly title: string; readonly description: string; readonly completed: boolean; }
 
type TodoPreview = Pick<Todo, "title" | "completed">;
// { title: string; completed: boolean; }
 
type TodoRecord = Record<"monday" | "tuesday", Todo>;
// { monday: Todo; tuesday: Todo; }

조건부 타입과 매핑된 타입의 결합

 조건부 타입과 매핑된 타입을 결합하면 더욱 복잡한 타입 변환이 가능합니다.

type ConditionallyNullable<T, K extends keyof T> = {
    [P in keyof T]: P extends K ? T[P] | null : T[P]
};
 
interface User {
    id: number;
    name: string;
    email: string;
}
 
type UserWithNullableEmail = ConditionallyNullable<User, "email">;
// { id: number; name: string; email: string | null; }

깊은 중첩 객체 타입 변환

 깊은 중첩 객체의 타입을 변환하는 것은 도전적일 수 있지만 재귀적 타입을 사용하여 해결할 수 있습니다.

type DeepPartial<T> = {
    [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K]
};
 
interface NestedObject {
    a: string;
    b: {
        c: number;
        d: {
            e: boolean;
        }
    }
}
 
type DeepPartialNested = DeepPartial<NestedObject>;
// {
//     a?: string;
//     b?: {
//         c?: number;
//         d?: {
//             e?: boolean;
//         }
//     }
// }

템플릿 리터럴 타입과 매핑된 타입

 템플릿 리터럴 타입과 매핑된 타입을 조합하면 강력한 문자열 기반 타입을 생성할 수 있습니다.

type Prefix<T, P extends string> = {
    [K in keyof T as `${P}${string & K}`]: T[K]
};
 
interface Person {
    name: string;
    age: number;
}
 
type PrefixedPerson = Prefix<Person, "user">;
// { userName: string; userAge: number; }

타입 안전 객체 변환 유틸리티

 매핑된 타입을 활용하여 타입 안전 객체 변환 유틸리티를 구현할 수 있습니다.

function mapObject<T, U>(obj: T, fn: <K extends keyof T>(key: K, value: T[K]) => U): { [K in keyof T]: U } {
    const result: any = {};
    for (const key in obj) {
        result[key] = fn(key, obj[key]);
    }
    return result;
}
 
const person = { name: "Alice", age: 30 };
const uppercasedPerson = mapObject(person, (_, value) => String(value).toUpperCase());
// { name: "ALICE", age: "30" }

성능 고려사항과 복잡성 관리

 매핑된 타입은 강력하지만, 과도하게 복잡한 매핑은 컴파일 시간을 증가시키고 IDE의 성능에 영향을 줄 수 있습니다.

 타입을 매핑할 때 다음과 같은 전략을 고려할 수 있습니다.

  1. 필요한 경우에만 복잡한 매핑을 사용합니다.
  2. 큰 타입에 대해 매핑을 수행할 때는 성능 영향을 테스트합니다.
  3. 복잡한 매핑은 더 작은 단위로 분해하여 관리합니다.

설계 원칙과 Best Practices

  1. 단순성 유지 : 가능한 한 간단한 매핑을 사용하세요. 복잡한 변환은 여러 단계로 나누어 구현하세요.
  2. 재사용성 고려 : 범용적으로 사용할 수 있는 매핑된 타입을 설계하세요.
  3. 명확한 이름 사용 : 매핑된 타입의 이름은 그 기능을 명확히 표현해야 합니다.
  4. 문서화 : 복잡한 매핑된 타입은 주석이나 문서를 통해 설명하세요.
  5. 테스트 작성 : 매핑된 타입의 결과를 검증하는 타입 테스트를 작성하세요.
  6. 조건부 타입 활용 : 필요한 경우 조건부 타입과 결합하여 더 유연한 매핑을 만드세요.
  7. 과도한 중첩 피하기 : 너무 깊은 중첩은 복잡성을 증가시키므로 주의하세요.
  8. 표준 라이브러리 활용 : 가능한 경우 내장 매핑 타입을 활용하세요.
  9. IDE 지원 고려 : 매핑된 타입이 IDE의 자동 완성과 타입 추론을 방해하지 않는지 확인하세요.
  10. 실제 사용 사례 기반 : 실제 문제 해결을 위해 매핑된 타입을 사용하고, 과도한 추상화는 피하세요.

 매핑된 타입은 타입스크립트에서 타입 시스템의 표현력을 크게 향상시키는 강력한 도구입니다.

 이를 통해 기존 타입을 기반으로 새로운 타입을 동적으로 생성하고, 복잡한 타입 변환을 수행할 수 있습니다.