Frontend/Typescript

맵드 타입(Mapped Types) 완벽 가이드: 기존 타입을 기반으로 새 타입 생성하기

미니임 2026. 2. 20. 00:04

 

타입스크립트를 사용하다 보면 비슷한 구조의 인터페이스를 여러 개 만들어야 하는 상황을 마주하게 됩니다. 예를 들어, 모든 필드가 필수인 타입이 있고, 이를 수정하기 위해 모든 필드가 선택 사항(?)인 타입이 필요한 경우입니다.

이때 **맵드 타입(Mapped Types)**을 사용하면 기존 타입을 가공하여 중복 없이 새로운 타입을 정의할 수 있습니다.

1. 맵드 타입이란?

맵드 타입은 자바스크립트 배열의 map 함수처럼, 타입 내의 프로퍼티들을 순회하며 새로운 프로퍼티 타입을 생성하는 문법입니다.

기본 문법

type NewType = {
  [K in keyof ExistingType]: NewPropertyType;
};
  • keyof ExistingType: 기존 타입의 모든 키(Property Names)를 가져옵니다.
  • K in ...: 가져온 키들을 하나씩 순회합니다 (마치 for...in 루프와 같습니다).
  • NewPropertyType: 각 키에 할당할 새로운 타입을 지정합니다.

2. 기초 예제: 모든 필드 변환하기

모든 값이 boolean인 타입을 string으로 변환하는 간단한 예제를 보겠습니다.

interface AppFeatures {
  isDarkMode: boolean;
  isLoggedIn: boolean;
  hasNotifications: boolean;
}

// 모든 필드를 string으로 바꾸는 맵드 타입
type FeatureLabels = {
  [K in keyof AppFeatures]: string;
};

/*
결과 타입:
type FeatureLabels = {
  isDarkMode: string;
  isLoggedIn: string;
  hasNotifications: string;
}
*/

const labels: FeatureLabels = {
  isDarkMode: "다크 모드 활성화 여부",
  isLoggedIn: "로그인 여부",
  hasNotifications: "알림 수신 여부"
};

3. 매핑 수정자(Mapping Modifiers) 활용하기

맵드 타입을 정의할 때 프로퍼티 앞에 readonly를 붙이거나, 뒤에 ?를 붙여 속성을 변경할 수 있습니다. 특히 - 기호를 사용하면 기존 속성을 제거할 수도 있습니다.

예제 1: 모든 필드를 읽기 전용 및 선택 사항으로 변경 (+)

interface User {
  id: number;
  name: string;
}

// readonly와 ?를 추가
type ReadonlyPartialUser = {
  readonly [K in keyof User]?: User[K];
};

/*
결과:
{
  readonly id?: number;
  readonly name?: string;
}
*/

예제 2: 속성 제거하기 (-)

기존 타입에 있던 readonly나 ?를 강제로 제거하고 싶은 경우 -를 사용합니다.

interface DraftArticle {
  id?: number;
  title?: string;
  readonly createdAt: Date;
}

// 선택 사항(?)과 readonly를 모두 제거하여 필수값으로 변경
type RequiredArticle = {
  -readonly [K in keyof DraftArticle]-?: DraftArticle[K];
};

/*
결과:
{
  id: number;
  title: string;
  createdAt: Date;
}
*/

4. 실무형 예제: 키 재매핑 (Key Remapping via as)

타입스크립트 4.1 버전부터 도입된 as 절을 사용하면 키의 이름을 변경하거나 필터링할 수 있습니다. 템플릿 리터럴 타입과 함께 사용하면 매우 강력합니다.

예제 3: Getter 메서드 타입 생성하기

인터페이스의 속성 이름을 기반으로 get이 붙은 메서드 리스트를 자동으로 생성해 봅시다.

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

// 키의 첫 글자를 대문자로 바꾸고 앞에 'get'을 붙임
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

type PersonGetters = Getters<Person>;

/*
결과:
{
  getName: () => string;
  getAge: () => number;
  getLocation: () => string;
}
*/

예제 4: 특정 타입의 필드만 필터링하기

as 절에서 never를 반환하면 해당 키는 결과 타입에서 제외됩니다.

interface MixedData {
  id: number;
  name: string;
  isActive: boolean;
  score: number;
}

// 오직 number 타입인 속성만 남기기
type OnlyNumbers<T> = {
  [K in keyof T as T[K] extends number ? K : never]: T[K];
};

type NumericData = OnlyNumbers<MixedData>;

/*
결과:
{
  id: number;
  score: number;
}
*/

5. 실무 활용: API 응답 및 폼 데이터 검증

실무에서는 API로부터 받은 데이터를 폼 데이터로 변환하거나, 각 필드에 대한 에러 메시지 타입을 정의할 때 자주 쓰입니다.

interface RegistrationForm {
  email: string;
  password: string;
  nickname: string;
}

// 각 필드의 유효성 검사 결과(에러 메시지)를 담는 타입
type ValidationResult<T> = {
  [K in keyof T]: {
    isValid: boolean;
    message: string;
  };
};

const formErrors: ValidationResult<RegistrationForm> = {
  email: { isValid: false, message: "이메일 형식이 아닙니다." },
  password: { isValid: true, message: "" },
  nickname: { isValid: true, message: "" }
};

요약

맵드 타입은 DRY(Don't Repeat Yourself) 원칙을 타입 시스템에 적용한 결과입니다.

  1. 기본 순회: [K in keyof T]를 통해 기존 타입을 복제하거나 변형합니다.
  2. 수정자 활용: readonly, ?를 추가하거나 -를 통해 제거합니다.
  3. 키 재매핑: as와 템플릿 리터럴을 사용해 프로퍼티 이름을 동적으로 생성합니다.
  4. 필터링: never와 조건부 타입을 조합해 특정 조건의 키만 추출합니다.

맵드 타입을 마스터하면 프로젝트의 스케일이 커져도 타입 관리가 매우 유연해지며, 휴먼 에러를 획기적으로 줄일 수 있습니다.

반응형