Skip to main content

Command Palette

Search for a command to run...

TypeScript 배우기 - 12. 매핑된 타입

Published
3 min read

TypeScript는 마치 매크로 언어처럼 기존 타입을 기반으로 새로운 타입을 쉽게 만들 수 있습니다.

매핑된 타입은 사전에 선언되지 않은 속성 타입을 선언하는데 사용하는 인덱스 서명 구문을 기반으로 합니다.

아래는 인덱스 서명 구문으로 표현된 예시입니다.

type OnlyBoolsAndHorses = {
  [key: string]: boolean | Horse;
};

const conforms: OnlyBoolsAndHorses = {
  del: true,
  rodney: false,
};

JavaScript에서 속성은 a["propertyName"]으로 표현될 수 있으므로 [key: string]: boolean | Horse로 표현할 수 있습니다. 그러므로 conformsdelrodneyboolean 타입을 반환하므로 정상 코드가 됩니다.

매핑된 타입은 in keyof를 사용해서 제네릭 인자의 타입을 순회하여 동일한 속성을 가진 새로운 타입을 정의합니다.

type OptionsFlags<Type> = {
  [Property in keyof Type]: boolean;
};

다음은 매핑된 타입의 예시입니다.

type FeatureFlags = {
  darkMode: () => void;
  newUserProfile: () => void;
};

// FeatureFlags의 속성 (함수 포함)을 그대로 받아 반환형을 `boolean`으로 바꿔줌
// type FeatureOptions = {
//    darMode: boolean;
//    newUserProfile: boolean;
// }
type FeatureOptions = OptionsFlags<FeatureFlags>;

매핑 수식어

readonly? 앞에 - 또는 +의 접두사를 붙일 수 있습니다. (+ 접두사는 생략 가능)

// Removes 'readonly' attributes from a type's properties
type CreateMutable<Type> = {
  // `-readonly`에 의해 `readonly`가 제거됨
  -readonly [Property in keyof Type]: Type[Property];
};

type LockedAccount = {
  readonly id: string;
  readonly name: string;
};

// type UnlockedAccount = {
//     id: string;
//     name: string;
// }
type UnlockedAccount = CreateMutable<LockedAccount>;
// Removes 'optional' attributes from a type's properties
type Concrete<Type> = {
  // `-?`에 의해 `?`가 제거됨
  [Property in keyof Type]-?: Type[Property];
};

type MaybeUser = {
  id: string;
  name?: string;
  age?: number;
};

// type USer = {
//    id: string;
//    name: string;
//    age: number;
// }
type User = Concrete<MaybeUser>;

as로 키 재매핑

TypeScript 4.1 이상부터 as 절을 이용해서 매핑된 타입의 키를 다시 매핑할 수 있습니다.

type MappedTypeWithNewProperties<Type> = {
    [Properties in keyof Type as NewKeyType]: Type[Properties]
}

다음은 템플릿 리터럴 타입과 결합해서 이전 속성에서 새로운 속성 이름을 만드는 예제 코드입니다.

type Getters<Type> = {
    [Property in keyof Type as `get${Capitalize<string & Property>}`]: () => Type[Property]
};

interface Person {
    name: string;
    age: number;
    location: string;
}

// type LazyPerson = {
//     getName: () => string;
//     getAge: () => number;
//     getLocation: () => string;
// }
type LazyPerson = Getters<Person>;

조건부 타입을 통해 never를 생성해서 키를 필터릴 할 수도 있습니다.

// Remove the 'kind' property
type RemoveKindField<Type> = {
    [Property in keyof Type as Exclude<Property, "kind">]: Type[Property]
};

interface Circle {
    kind: "circle";
    radius: number;
}

// type KindlessCircle = {
//     radius: number;
// }
type KindlessCircle = RemoveKindField<Circle>;

string | number | symbol 유니온 타입 뿐만 아니라 다른 유니온 타입에 대해 임의의 매핑을 할 수 있습니다.

type EventConfig<Events extends { kind: string }> = {
    [E in Events as E["kind"]]: (event: E) => void;
}

type SquareEvent = { kind: "square", x: number, y: number };
type CircleEvent = { kind: "circle", radius: number };

// type Config = {
//      square: (event: SquareEvent) => void;
//      circle: (event: CircleEvent) => void;
// }
type Config = EventConfig<SquareEvent | CircleEvent>

대단하군요. 매크로 언어와 같은 느낌입니다.

추가 탐색

매핑된 타입은 다른 조작 섹션의 기능과 잘 결합하여 작동합니다. 다음은 pii 속성이 리터럴 true로 설정되어 있는지의 유무에 따라 true 또는 false를 반환하는 조건부 타입을 사용하는 매핑된 타입의 예시입니다.

type ExtractPII<Type> = {
// Type의 키를 순회하며
// `{ pii: true }`가 있는 경우 true, 없는 경우 false 타입이 됨
  [Property in keyof Type]: Type[Property] extends { pii: true } ? true : false;
};

type DBFields = {
  id: { format: "incrementing" };
  name: { type: string; pii: true };
};

// type ObjectsNeedingGDPRDeletion = {
//     id: false;
//     name: true;
// }
type ObjectsNeedingGDPRDeletion = ExtractPII<DBFields>;
24 views

More from this blog

개발, 테스트, 운영에서의 도커 활용

핵심 원칙: "한 번 빌드하고, 어디서든 실행한다 (Build once, run anywhere)" 도커의 가장 큰 장점은 환경 일관성입니다. 동일한 도커 이미지를 사용하여 개발, 테스트, 운영 환경을 구성함으로써 "제 PC에서는 됐는데..." 하는 문제를 최소화할 수 있습니다. 1. 개발 단계 (Development) 목표: 빠른 코드 변경 반영, 쉬운 디버깅, 실제 운영 환경과 유사한 환경 구성. Docker 사용 방안: Dockerf...

May 9, 20256 min read15

[EF Core] 데이터 삭제 시 소프트 삭제 적용

DB에서 데이터를 삭제하면 일반적으로 복구할 수 없습니다. 또한 관계에 따라 영구 삭제 자체가 어려울 수도 있습니다. 그래서 데이터를 영구 삭제하는 대신 IsDeleted 속성을 true로 주고 IsDeleted 속성을 필터링해서 조회하는 방법을 사용하기도 합니다. 이를 소프트 삭제라고 합니다. 그런데 EF에서 알아서 데이터 삭제 시 소프트 삭제를 하고 쿼리시 IsDeleted 속성을 체크해서 삭제한 데이터를 제외한 데이터만 쿼리하게 하는 ...

Mar 18, 20243 min read19

[EF Core] ValueConverter를 이용해서 엔터티 속성의 도메인 관리

EF Core를 사용하면서 문자열 길이 등의 특성을 일일이 지정하는 것은 번거롭습니다. ... [MaxLength(32)] public string? 제목 { get; set; } 엔터티가 한 개일 때는 상관이 없으나 제목 유형이 여러 엔터티에 사용될 경우 유형을 지정하기 번거롭습니다. 속성 유형을 도메인으로 관리하면 참 편할텐데요, ValueConverter를 이용할 수 있습니다. 그런데 이것을 인터페이스 정적 추상를 사용해서 다음처럼 ...

Mar 16, 20242 min read8

디모이 블로그

154 posts

.NET 관련 기술을 선호하고 새로운 언어를 배우는데 관심이 있습니다.