[TypeScript] Utility Types
정의
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-fest 의 ReadonlyDeep.
필드 선택
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">;
Omit 은 Pick + 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: 깊은 readonlyPartialDeep: 깊은 partialSetOptional<T, K>: 특정 필드만 optionalSetRequired<T, K>: 특정 필드만 requiredMerge<A, B>: 두 타입 병합 (B 우선)CamelCase<S>,SnakeCase<S>,KebabCase<S>Simplify<T>: 표시 단순화Except<T, K>: 엄격한 OmitValueOf<T>: T 의 값 타입 unionEntries<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 형태로 감싸는 것 자주 필요.
관련 위키
- TypeScript - 상위 개요
- Mapped Types - Utility 구현
- Conditional Types - Utility 구현
- Generics
- Interfaces
- Type Aliases
- Type Narrowing - NonNullable
- Template Literal Types - 문자열 변형
- Strict Mode - noUncheckedIndexedAccess
이 글의 용어 (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, …
💬 댓글