본문으로 건너뛰기

안동민 개발노트

본문 시작

유틸리티 타입

Partial·Required·Readonly·Pick·Omit과 유니온·함수 변환 유틸리티로 기존 타입을 목적에 맞게 재구성합니다.

타입스크립트는 복잡한 타입 변환/조합을 쉽게 수행하도록 다양한 유틸리티 타입(Utility Types)을 내장하고 있습니다.

이 유틸리티 타입 대부분은 앞서 배운 조건부 타입과 매핑된 타입 개념(keyof, typeof, infer 등)을 기반으로 구현되어 있습니다.

유틸리티 타입은 기존 타입을 재활용하여 새로운 타입을 생성하는 데 탁월하며, 타입 정의의 반복을 줄이고 코드의 가독성 및 유지보수성을 크게 향상시킵니다.

이 절에서는 가장 자주 사용되는 주요 유틸리티 타입들을 살펴보겠습니다.


객체 타입 변환 유틸리티

유틸리티 타입 변환 지도

MODIFIER · KEYS · UNION · SIGNATURE

유틸리티 타입 변환 지도

유틸리티 이름을 외우기보다 어떤 축을 변환하는지와 mapped/conditional/infer 원리를 연결한다.

유틸리티 타입 변환 지도 source type에서 modifier 변환, key projection/construction, union filtering, function/awaited extraction의 네 축으로 utility를 분류하고 mapped/conditional/infer 구현 원리를 연결한다. SOURCE 원본 타입 관계 property · union · function MODIFIER Partial · Required Readonly · -? · readonly KEY SET Pick · Omit · Record projection · construction UNION Exclude · Extract NonNullable · conditional SIGNATURE Parameters · ReturnType Awaited · infer RESULT 파생 타입 view 런타임 값은 그대로
  1. 원본 타입 관계

    SOURCE — property · union · function

  2. Partial · Required

    MODIFIER — Readonly · -? · readonly

  3. Pick · Omit · Record

    KEY SET — projection · construction

  4. Exclude · Extract

    UNION — NonNullable · conditional

  5. Parameters · ReturnType

    SIGNATURE — Awaited · infer

  6. 파생 타입 view

    RESULT — 런타임 값은 그대로

유틸리티 타입은 타입 표현을 계산한다. 값을 채우거나 삭제하거나 freeze하거나 bind하지 않는다.

객체 타입의 속성을 수정하거나 특정 속성만 선택/제외할 때 사용하는 유틸리티 타입입니다.

Partial<T>: T의 모든 속성을 선택적(Optional) 으로 만듭니다.

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

type PartialUser = Partial<User>;
/*
// PartialUser의 실제 타입:
type PartialUser = {
  id?: number;
  name?: string;
  email?: string;
};
*/
const userUpdate: PartialUser = {
  name: "새로운 이름",
  email: "new@example.com"
}; // id는 필수가 아님

폼 데이터 업데이트 등, 객체의 일부만 필요한 경우에 유용합니다.

Required<T>: T의 모든 속성을 필수(Required)로 만듭니다.

Partial의 반대입니다.

interface ProductConfig {
  name?: string;
  price?: number;
  inStock?: boolean;
}

type FullProductConfig = Required<ProductConfig>;
/*
// FullProductConfig의 실제 타입:
type FullProductConfig = {
  name: string;
  price: number;
  inStock: boolean;
};
*/
// const config1: FullProductConfig = { name: "Test" }; // Error: 'price' 및 'inStock' 속성이 누락됨
const config2: FullProductConfig = { name: "Monitor", price: 300000, inStock: true };

Readonly<T>: T의 모든 속성을 읽기 전용(readonly) 으로 만듭니다.

interface Point {
  x: number;
  y: number;
}

type ImmutablePoint = Readonly<Point>;
/*
// ImmutablePoint의 실제 타입:
type ImmutablePoint = {
  readonly x: number;
  readonly y: number;
};
*/
const p: ImmutablePoint = { x: 10, y: 20 };
// p.x = 15; // Error: Cannot assign to 'x' because it is a read-only property.

객체의 불변성을 강제할 때 사용됩니다.

Pick<T, K>: T에서 K 타입에 해당하는 속성들만 선택하여 새로운 타입을 만듭니다.

interface Todo {
  id: number;
  title: string;
  description: string;
  completed: boolean;
  createdAt: Date;
}

type TodoPreview = Pick<Todo, 'id' | 'title' | 'completed'>;
/*
// TodoPreview의 실제 타입:
type TodoPreview = {
  id: number;
  title: string;
  completed: boolean;
};
*/
const todoItem: TodoPreview = {
  id: 1,
  title: "타입스크립트 학습",
  completed: false
};

특정 API 응답에서 필요한 데이터만 추출하거나, 특정 뷰에 필요한 데이터 모델을 정의할 때 유용합니다.

Omit<T, K>: T에서 K 타입에 해당하는 속성들을 제외하여 새로운 타입을 만듭니다.

type TodoWithoutDescription = Omit<Todo, 'description'>;
/*
// TodoWithoutDescription의 실제 타입:
type TodoWithoutDescription = {
  id: number;
  title: string;
  completed: boolean;
  createdAt: Date;
};
*/
const todoDisplay: TodoWithoutDescription = {
  id: 2,
  title: "회의 준비",
  completed: true,
  createdAt: new Date()
};

Pick과 상호 보완적으로 사용되며, 특정 속성을 제외한 나머지가 필요할 때 유용합니다.

Record<K, T>: K 타입의 속성 이름을 가지며, 모든 속성 값이 T 타입인 객체 타입을 만듭니다.

딕셔너리 또는 맵 타입을 정의할 때 사용됩니다.

type PageInfo = {
  title: string;
  url: string;
};

type Page = "home" | "about" | "contact";

type SitePages = Record<Page, PageInfo>;
/*
// SitePages의 실제 타입:
type SitePages = {
  home: { title: string; url: string; };
  about: { title: string; url: string; };
  contact: { title: string; url: string; };
};
*/
const site: SitePages = {
  home: { title: "메인 페이지", url: "/" },
  about: { title: "회사 소개", url: "/about" },
  contact: { title: "연락처", url: "/contact" }
};

동적으로 키가 생성되거나, 특정 키 집합에 대해 일관된 값 타입을 강제할 때 강력합니다.


유니온 및 함수 타입 변환 유틸리티

유니온 타입이나 함수 타입을 다룰 때 유용한 유틸리티입니다.

Exclude<T, U>: T에서 U에 할당 가능한 타입을 제외합니다. (5장 2절 "조건부 타입"에서 설명)

type MyColors = "red" | "green" | "blue" | "black" | "white";
type PrimaryColors = Exclude<MyColors, "black" | "white">; // type PrimaryColors = "red" | "green" | "blue"

Extract<T, U>: T에서 U에 할당 가능한 타입만 추출합니다. (5장 2절 "조건부 타입"에서 설명)

type MixedValue = string | number | boolean | (() => void);
type OnlyFunctions = Extract<MixedValue, Function>; // type OnlyFunctions = () => void

NonNullable<T>: T에서 nullundefined제외합니다. (5장 2절 "조건부 타입"에서 설명)

type NullableString = string | null | undefined;
type NonNullString = NonNullable<NullableString>; // type NonNullString = string

Parameters<T>: 함수 타입 T매개변수 타입을 튜플로 추출합니다.

type MyFunction = (name: string, age: number) => string;
type Params = Parameters<MyFunction>; // type Params = [name: string, age: number]

type NoParams = Parameters<() => void>; // type NoParams = []

ReturnType<T>: 함수 타입 T반환 타입을 추출합니다.

type MyFunctionReturn = ReturnType<MyFunction>; // type MyFunctionReturn = string
type AsyncReturn = ReturnType<() => Promise<number>>; // type AsyncReturn = Promise<number>

내장 유틸리티 타입은 이름은 많지만, 내부 원리는 매핑된 타입, 조건부 타입, infer 추론으로 묶어 볼 수 있습니다.


고급 유틸리티 타입 (일부)

타입스크립트에는 이 외에도 특정 상황에서 유용한 고급 유틸리티 타입들이 있습니다.

ThisParameterType<T>: 함수 타입 Tthis 매개변수 타입을 추출합니다.

function toHex(this: Number, decimal: number): string {
  return this.toString(16) + decimal.toString(16);
}
type ThisType = ThisParameterType<typeof toHex>; // type ThisType = Number

OmitThisParameter<T>: 함수 타입 T에서 this 매개변수를 제거한 새로운 함수 타입을 만듭니다.

type NoThisToHex = OmitThisParameter<typeof toHex>; // type NoThisToHex = (decimal: number) => string
// const simplifiedHex: NoThisToHex = toHex; // Error: 'this' 매개 변수가 포함된 함수는 바인딩되어야 합니다.
                                            // 타입만 제거할 뿐 런타임 동작이 변하는 것은 아닙니다.

Awaited<T> (TypeScript 4.5+): Promise와 같은 awaitable 타입의 최종 resolved 값을 재귀적으로 추출합니다.

type PromiseOfNumbers = Promise<Promise<number>>;
type Result = Awaited<PromiseOfNumbers>; // type Result = number

type ValueOrPromise = string | Promise<number>;
type AwaitedValueOrPromise = Awaited<ValueOrPromise>; // type AwaitedValueOrPromise = string | number

비동기 함수의 반환 타입을 정확히 추론할 때 매우 유용합니다.

아래 다이어그램은 하나의 원본 타입에서 수정 DTO, 공개 DTO, 함수 계약, 비동기 결과 타입을 파생시키는 조합을 보여줍니다.

유틸리티 타입과 데이터 런타임 경계

VALIDATE · MODEL · SERIALIZE

유틸리티 타입과 데이터 런타임 경계

unknown 요청과 공개 응답은 실제 변환을 수행하는 validator와 allowlist serializer가 책임지고 utility는 각 단계의 정적 view만 파생한다.

유틸리티 타입과 데이터 런타임 경계 unknown request를 runtime validator가 완전한 command로 만들고, 내부 model에서 allowlist serializer가 public value를 실제 생성하는 동안 Partial/Pick/Omit/Required/Readonly는 각 단계의 compile-time view만 파생한다는 경계를 보여 준다. REQUEST unknown 입력 키와 값이 미검증 VALIDATOR 허용 key/value 검사 완전한 command 생성 MODEL 내부 User password 포함 가능 UTILITY Pick · Partial view 컴파일 시 관계만 SERIALIZER allowlist로 새 값 생성 public 필드만 복사 RESPONSE 실제 공개 객체 민감 own property 없음
  1. unknown 입력

    REQUEST — 키와 값이 미검증

  2. 허용 key/value 검사

    VALIDATOR — 완전한 command 생성

  3. 내부 User

    MODEL — password 포함 가능

  4. Pick · Partial view

    UTILITY — 컴파일 시 관계만

  5. allowlist로 새 값 생성

    SERIALIZER — public 필드만 복사

  6. 실제 공개 객체

    RESPONSE — 민감 own property 없음

Omit 주석은 런타임 속성을 제거하지 않는다. 보안 경계에서는 blocklist보다 명시적 allowlist 값을 생성한다.

유틸리티 타입은 원본 타입을 직접 바꾸지 않고 목적별 계약을 파생시키는 도구입니다.

다음 기준표는 API 입력, 공개 응답, 함수 계약, 비동기 결과에 맞는 유틸리티 선택을 정리합니다.


유틸리티 타입은 기존 타입을 선택, 제외, 부분화, 읽기 전용화하는 재사용 가능한 타입 변환 도구입니다.

내장 유틸리티 타입을 사용하면 반복적인 타입 선언을 줄이고, 객체 구조 변화에 맞춰 타입 계약을 더 일관되게 유지할 수 있습니다.


다음 다이어그램은 TypeScript 유틸리티 타입을 변환 대상별로 묶어 보는 선택표입니다.

이 다이어그램은 유틸리티 타입 학습을 객체 변환, 유니온 필터, 함수 추출 기준으로 마무리합니다.

아래 다이어그램은 자주 쓰는 유틸리티 타입을 객체 변환, 유니온 필터, 함수 추출 기준으로 묶습니다.

아래 다이어그램은 유틸리티 타입을 실제 코드에 적용하기 전에 설정 위치, 타입 해석, 빌드 결과를 확인합니다.