본문으로 건너뛰기
김신건의 로그

[TypeScript] Utility Types

· 수정 · 📖 약 2분 · 619자/단어 #typescript #types #utility #standard-library
TypeScript Utility Types, TS Utility Types, Partial, Required, Readonly, Pick, Omit, Record, Exclude, Extract, NonNullable, ReturnType, Parameters, Awaited, TypeScript 유틸리티 타입

정의

Utility Types 는 TypeScript 표준 라이브러리에 포함된 미리 정의된 generic 타입 들입니다. 기존 타입을 변형/구성하는 흔한 패턴을 제공하며, mapped/conditional 타입으로 구현되어 있습니다.

유틸리티 타입 계보

다이어그램 1: Object 변형 유틸리티 타입

flowchart LR
    MT["Mapped Type 구현"]
    CT["Conditional Type 구현"]
    MT --> P["Partial of T"]
    MT --> RQ["Required of T"]
    MT --> RO["Readonly of T"]
    MT --> PI["Pick of T, K"]
    MT --> OM["Omit of T, K"]
    CT --> EX["Exclude of T, U"]
    CT --> ET["Extract of T, U"]
    CT --> NN["NonNullable of T"]
    PI -->|"여집합"| OM

다이어그램 2: 함수/Promise 유틸리티 타입

flowchart TD
    F["Function F"] --> RT["ReturnType of F"]
    F --> PR["Parameters of F"]
    RT --> R1["반환 타입 추출"]
    PR --> R2["매개변수 tuple 추출"]
    P1["Promise of string"] -->|"단층 unwrap"| AW["Awaited of T"]
    P2["Promise of Promise of number"] -->|"재귀 unwrap"| AW
    AW --> R3["최종 내부 타입"]

Object 변형

Partial<T> - 모든 필드를 optional

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

type UserUpdate = Partial<User>;
// = {id?: number; name?: string; email?: string}

function update(id: number, patch: Partial<User>): void { ... }

구현:

type Partial<T> = {[K in keyof T]?: T[K]};

Required<T> - 모든 필드를 required

interface Config {port?: number; debug?: boolean}

type StrictConfig = Required<Config>;
// = {port: number; debug: boolean}

구현:

type Required<T> = {[K in keyof T]-?: T[K]};

-? 는 optional 을 제거.

Readonly<T> - 모든 필드를 readonly

type Point = {x: number; y: number};
type FrozenPoint = Readonly<Point>;

const p: FrozenPoint = {x: 1, y: 2};
p.x = 5;   // 오류

Shallow only. 중첩 객체는 여전히 mutable. Deep readonly 는 type-festReadonlyDeep.

필드 선택

Pick<T, K> - 특정 필드만

type User = {id: number; name: string; email: string; password: string};

type UserPublic = Pick<User, "id" | "name" | "email">;
// = {id: number; name: string; email: string}

Omit<T, K> - 특정 필드 제외

type UserPublic = Omit<User, "password">;
// = {id: number; name: string; email: string}

type UserUpdate = Omit<User, "id">;

OmitPick + Exclude 조합.

Record (사전)

Record<K, V> - K 키의 사전

type UserRoles = Record<string, "admin" | "user" | "guest">;

const roles: UserRoles = {
  alice: "admin",
  bob: "user",
};

// 리터럴 키
type Status = Record<"pending" | "active" | "closed", number>;
// = {pending: number; active: number; closed: number}

구현:

type Record<K extends keyof any, T> = {[P in K]: T};

Union 조작

Exclude<T, U> - T 에서 U 제거

type Direction = "north" | "south" | "east" | "west";
type Horizontal = Exclude<Direction, "north" | "south">;
// = "east" | "west"

Extract<T, U> - T 에서 U 와 겹치는 것만

type Value = string | number | boolean | null;
type Primitive = Extract<Value, string | number>;
// = string | number

NonNullable<T> - null / undefined 제거

type Maybe = string | null | undefined;
type Value = NonNullable<Maybe>;
// = string

Function 관련

ReturnType<F> - 함수 반환 타입

function fetch(): Promise<User> { ... }

type FetchResult = ReturnType<typeof fetch>;
// = Promise<User>

Parameters<F> - 함수 매개변수 타입 (tuple)

function log(level: string, msg: string, data?: object): void { ... }

type LogParams = Parameters<typeof log>;
// = [string, string, object?]

function proxy(...args: Parameters<typeof log>): void {
  log(...args);
}

ConstructorParameters<C> - 생성자 매개변수

class User {
  constructor(name: string, age: number) {}
}

type UserArgs = ConstructorParameters<typeof User>;
// = [string, number]

InstanceType<C> - 클래스 인스턴스 타입

class User { constructor(name: string) {} }

type UserInstance = InstanceType<typeof User>;
// = User

typeof Class 는 constructor 타입, InstanceType 은 그 인스턴스.

ThisParameterType<F> / OmitThisParameter<F>

function fn(this: string): number { return this.length }

type T = ThisParameterType<typeof fn>;    // string
type F = OmitThisParameter<typeof fn>;    // () => number

Promise 관련

Awaited<T> - Promise 를 unwrap (재귀)

type A = Awaited<Promise<string>>;              // string
type B = Awaited<Promise<Promise<number>>>;      // number (재귀)
type C = Awaited<string>;                        // string (non-Promise 는 그대로)
async function getUser(): Promise<User> { ... }

type User = Awaited<ReturnType<typeof getUser>>;   // User

TS 4.5+.

String 조작

Uppercase<S>, Lowercase<S>, Capitalize<S>, Uncapitalize<S>

type A = Uppercase<"hello">;       // "HELLO"
type B = Lowercase<"HELLO">;        // "hello"
type C = Capitalize<"hello">;       // "Hello"
type D = Uncapitalize<"Hello">;     // "hello"

// Template literal 과 조합
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">;  // "onClick"

자세한 것은 Template Literal Types 참조.

Composition 예시

type User = {
  id: number;
  email: string;
  password: string;
  createdAt: Date;
  updatedAt: Date;
};

// 생성 시: 모든 필드 필수, id/timestamp 자동
type UserCreateInput = Omit<User, "id" | "createdAt" | "updatedAt">;

// 갱신 시: id 제외 + Partial
type UserUpdateInput = Partial<Omit<User, "id" | "createdAt" | "updatedAt">>;

// 응답 시: 민감 필드 제외
type UserResponse = Omit<User, "password">;

// Public read-only
type UserPublicRO = Readonly<Pick<User, "id" | "email">>;

실전 patterns

API endpoint 정의

type Endpoints = {
  "GET /users": {response: User[]};
  "GET /users/:id": {params: {id: string}; response: User};
  "POST /users": {body: UserCreateInput; response: User};
  "PATCH /users/:id": {params: {id: string}; body: UserUpdateInput; response: User};
};

type Method<E extends keyof Endpoints> =
  E extends `${infer M} ${string}` ? M : never;

type ExtractParams<E> = E extends {params: infer P} ? P : never;
type ExtractBody<E> = E extends {body: infer B} ? B : never;
type ExtractResponse<E> = E extends {response: infer R} ? R : never;

form input mapping

type FormInputs<T> = {
  [K in keyof T]: {
    label: string;
    type: T[K] extends boolean ? "checkbox" :
          T[K] extends number ? "number" :
          "text";
    required: undefined extends T[K] ? false : true;
  };
};

type UserFormInputs = FormInputs<User>;

type-fest 확장

Sindre Sorhus 의 type-fest 는 표준 utility 로 부족한 것들을 추가:

  • ReadonlyDeep: 깊은 readonly
  • PartialDeep: 깊은 partial
  • SetOptional<T, K>: 특정 필드만 optional
  • SetRequired<T, K>: 특정 필드만 required
  • Merge<A, B>: 두 타입 병합 (B 우선)
  • CamelCase<S>, SnakeCase<S>, KebabCase<S>
  • Simplify<T>: 표시 단순화
  • Except<T, K>: 엄격한 Omit
  • ValueOf<T>: T 의 값 타입 union
  • Entries<T>: [key, value] tuple 배열

만들기 있어 유용

DeepReadonly

type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

DeepPartial

type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};

PickByValue (값 타입으로 필드 선택)

type PickByValue<T, V> = {
  [K in keyof T as T[K] extends V ? K : never]: T[K];
};

type User = {id: number; name: string; age: number};
type Numeric = PickByValue<User, number>;   // {id: number; age: number}

KeysOfType

type KeysOfType<T, V> = {
  [K in keyof T]: T[K] extends V ? K : never;
}[keyof T];

type NumberKeys = KeysOfType<User, number>;   // "id" | "age"

RequireAtLeastOne

type RequireAtLeastOne<T, Keys extends keyof T = keyof T> =
  Pick<T, Exclude<keyof T, Keys>>
  & {
      [K in Keys]-?:
        Required<Pick<T, K>>
        & Partial<Pick<T, Exclude<Keys, K>>>
  }[Keys];

함정

WARNING

Readonly 는 shallow. 중첩 객체는 여전히 mutable.

CAUTION

Omit 은 union 에 배타적. Omit<A | B, "id"> 는 A 와 B 각각에서 id 제거가 아니라 공통 필드만 유지. 원하는 게 각 case 별 제거면 distributive helper.

WARNING

Record<string, T> 는 index signature. keyof 하면 string. 명시적 유한 union 이 필요하면 리터럴 union 을 keys 로.

IMPORTANT

Utility type 은 매번 재계산 가능. 자주 쓰는 조합은 alias 로 저장해 컴파일러 캐시 활용.

CAUTION

ReturnType<T> 은 T 가 함수 타입일 때만. typeof someFunction 형태로 감싸는 것 자주 필요.

관련 위키

이 글의 용어 (9개)
[TypeScript] Conditional Typestypescript
정의 Conditional Type 은 형태로 타입 레벨에서 조건 분기를 표현합니다. 키워드로 매칭된 타입을 추출하고, union 에 대해서는 자동 분배 (distributive…
[TypeScript] Genericstypescript
정의 Generics 는 타입을 파라미터화하는 문법입니다. 함수, 클래스, 인터페이스, type alias 가 재사용 가능 하면서도 타입 안전 하도록 만듭니다. Java 의 ge…
[TypeScript] Interfacestypescript
정의 Interface 는 객체의 shape (형태) 을 정의하는 TypeScript 문법입니다. 필드, 메서드, 인덱스 시그니처, 호출 시그니처를 선언하며, 로 상속하고 dec…
[TypeScript] Mapped Typestypescript
정의 Mapped Type 은 기존 타입의 각 필드를 순회 ( ) 하며 새 타입을 생성하는 문법입니다. , modifier 를 붙이거나 제거 ( , ) 하고, clause 로 키…
[TypeScript] Strict Mode & tsconfigtypescript
정의 Strict Mode 는 여러 엄격한 타입 검사 플래그를 한 번에 켜는 옵션 ( ) 입니다. 강력한 타입 안전성을 제공하지만, 기존 코드베이스에는 대량의 오류를 발생시킬 수…
[TypeScript] Template Literal Typestypescript
정의 Template Literal Type 은 TypeScript 4.1 에서 도입된 문법으로, JavaScript template literal 처럼 백틱과 를 이용해 타입 …
[TypeScript] Type Aliasestypescript
정의 Type Alias 는 타입에 이름을 붙이는 TypeScript 문법 ( ). Interface 는 객체 shape 만 표현하지만, type alias 는 모든 타입 표현식…
[TypeScript] Type Narrowing (Type Guards)typescript
정의 Type Narrowing 은 TypeScript 컴파일러가 제어 흐름 분석 (control flow analysis) 을 통해 특정 위치에서 값의 타입을 더 좁게 추론하는…
TypeScripttypescript
정의 TypeScript 는 Microsoft 가 2012년 발표한 JavaScript 의 상위 집합 프로그래밍 언어입니다. 정적 타입 시스템, 인터페이스, 제네릭, enum, …

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기