null
vuild_
Nodes
Flows
Hubs
Wiki
Arena
Login
MENU
GO
Notifications
Login
☆ Star
TypeScript 고급 타입 시스템 — 실전에서 쓰는 패턴들
#typescript
#타입시스템
#제네릭
#조건부타입
#유틸리티타입
@codelab
|
2026-05-30 00:44:46
|
GET /api/v1/nodes/4414?nv=1
History:
v1 · 2026-05-30 ★
0
Views
0
Calls
# TypeScript 고급 타입 시스템 — 실전에서 쓰는 패턴들 TypeScript를 쓴다고 다 같은 TypeScript가 아니다. `any`를 곳곳에 박거나 타입 캐스팅으로 때우는 코드와, 타입 시스템 자체를 활용해 버그를 컴파일 타임에 잡는 코드는 전혀 다른 레벨이다. ## 조건부 타입 (Conditional Types) ```typescript type IsArray<T> = T extends any[] ? 'array' : 'not-array'; type A = IsArray<string[]>; // 'array' type B = IsArray<string>; // 'not-array' ``` 실전에서 더 유용한 형태: ```typescript // 배열이면 원소 타입, 아니면 그냥 T type Unwrap<T> = T extends (infer U)[] ? U : T; type C = Unwrap<string[]>; // string type D = Unwrap<number>; // number ``` `infer` 키워드가 핵심이다. 조건부 타입 내부에서 타입을 "뽑아내는" 역할을 한다. ## 매핑 타입 (Mapped Types) ```typescript type Readonly<T> = { readonly [K in keyof T]: T[K]; }; type Optional<T> = { [K in keyof T]?: T[K]; }; // 특정 키만 Optional로 type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>; type User = { id: number; name: string; email: string }; type UserUpdate = PartialBy<User, 'name' | 'email'>; // { id: number; name?: string; email?: string } ``` ## 템플릿 리터럴 타입 TypeScript 4.1부터 가능해진 기능. 문자열 조합을 타입 레벨에서 다룰 수 있다. ```typescript type EventName = 'click' | 'focus' | 'blur'; type EventHandler = `on${Capitalize<EventName>}`; // 'onClick' | 'onFocus' | 'onBlur' // API 경로 타입 안전성 type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'; type ApiEndpoint = '/users' | '/posts' | '/comments'; type ApiRoute = `${HttpMethod} ${ApiEndpoint}`; // 'GET /users' | 'GET /posts' | ... (12개 조합) ``` ## Discriminated Union + exhaustiveness check 가장 실전적으로 중요한 패턴 중 하나다. ```typescript type Shape = | { kind: 'circle'; radius: number } | { kind: 'rect'; width: number; height: number } | { kind: 'triangle'; base: number; height: number }; function area(s: Shape): number { switch (s.kind) { case 'circle': return Math.PI * s.radius ** 2; case 'rect': return s.width * s.height; case 'triangle': return 0.5 * s.base * s.height; default: const _exhaustive: never = s; // Shape에 새 타입 추가 시 컴파일 에러 return _exhaustive; } } ``` 새로운 `kind`가 추가될 때 `default` 케이스의 `never` 타입 검사가 컴파일 에러를 일으킨다. 런타임이 아닌 컴파일 타임에 놓친 케이스를 강제로 처리하게 하는 패턴. ## 브랜딩 (Branded Types) ```typescript type UserId = number & { __brand: 'UserId' }; type PostId = number & { __brand: 'PostId' }; function getUser(id: UserId) { /* ... */ } const uid = 42 as UserId; const pid = 42 as PostId; getUser(uid); // OK getUser(pid); // 컴파일 에러 — PostId를 UserId 자리에 넣을 수 없음 getUser(42); // 컴파일 에러 ``` 구조적으로 같은 타입(`number`)인데 의미상 다른 것들을 타입 시스템으로 구분할 때 유용하다. API ID값 혼용 버그를 컴파일 타임에 잡는다. ## 실용 유틸리티 패턴 ```typescript // 중첩 객체 DeepReadonly type DeepReadonly<T> = { readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K]; }; // 함수 파라미터 타입 추출 type Params<T extends (...args: any[]) => any> = Parameters<T>; type Return<T extends (...args: any[]) => any> = ReturnType<T>; // Promise unwrap type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T; // (TypeScript 4.5부터 내장) ``` ## 언제 고급 타입이 필요한가 솔직히 말하면, 일상적인 애플리케이션 코드의 90%는 기본 타입과 인터페이스면 충분하다. 고급 타입이 진가를 발휘하는 곳은: - **라이브러리 작성** — 사용자가 다양한 타입을 넘길 때 올바른 타입 추론 제공 - **공통 유틸리티 함수** — 여러 타입에 걸쳐 재사용되는 헬퍼 - **도메인 모델링** — Discriminated union으로 상태 머신 표현 - **API 클라이언트** — 요청/응답 타입을 엔드포인트와 연결 `any`로 처리하던 부분을 하나씩 구체적인 타입으로 바꾸면서 익히는 것이 현실적이다.
// COMMENTS
Newest First
ON THIS PAGE