SHIN
TypeScript 실전 팁 10선
10편T extends Something으로 제네릭 타입이 가져야 할 최소 구조를 지정합니다.
// ❌ 제약 없음 — T에 .length가 있다는 보장 없음
function longest<T>(a: T, b: T): T {
return a.length >= b.length ? a : b // 컴파일 오류
}
// ✅ { length: number }를 가진 타입으로 제약
function longest<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b
}
longest('abc', 'de') // ✅ string (length 있음)
longest([1, 2], [3]) // ✅ number[] (length 있음)
longest({ length: 3 }, { length: 1 }) // ✅
longest(10, 20) // ❌ number는 length 없음// K는 반드시 T의 키여야 함
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
const user = { id: 1, name: '홍길동', age: 30 }
const name = getProperty(user, 'name') // ✅ 반환 타입: string
const id = getProperty(user, 'id') // ✅ 반환 타입: number
getProperty(user, 'email') // ❌ 'email'은 키가 아님// T가 string이면 'text', 아니면 'other'
type TypeName<T> = T extends string ? 'text' : 'other'
type A = TypeName<string> // 'text'
type B = TypeName<number> // 'other'
// 배열 요소 타입 추출
type ElementType<T> = T extends (infer U)[] ? U : never
type E1 = ElementType<string[]> // string
type E2 = ElementType<number[][]> // number[]
type E3 = ElementType<string> // never// Promise 내부 타입 추출
type Unwrap<T> = T extends Promise<infer U> ? U : T
type A = Unwrap<Promise<string>> // string
type B = Unwrap<Promise<number[]>> // number[]
type C = Unwrap<string> // string (Promise 아님)
// 함수 첫 번째 파라미터 타입 추출
type FirstParam<T extends (...args: any) => any> =
T extends (first: infer F, ...rest: any) => any ? F : never
type F1 = FirstParam<(a: string, b: number) => void> // string
type F2 = FirstParam<(x: boolean) => void> // booleantype DeepReadonly<T> = T extends (infer U)[]
? ReadonlyArray<DeepReadonly<U>>
: T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T
interface Config {
server: {
host: string
ports: number[]
}
}
const cfg: DeepReadonly<Config> = {
server: { host: 'localhost', ports: [3000, 8080] }
}
cfg.server.host = 'prod' // ❌ 읽기 전용
cfg.server.ports.push(9000) // ❌ ReadonlyArray// API 핸들러 맵 타입
type HandlerMap<T extends Record<string, any>> = {
[K in keyof T]: (payload: T[K]) => void
}
interface Events {
userCreated: { id: number; name: string }
userDeleted: { id: number }
postPublished: { postId: string; title: string }
}
const handlers: HandlerMap<Events> = {
userCreated: ({ id, name }) => console.log(id, name), // ✅ 타입 추론
userDeleted: ({ id }) => console.log(id),
postPublished: ({ postId, title }) => console.log(postId, title),
}제네릭 제약과 infer를 조합하면 라이브러리 수준의 재사용 가능한 타입 유틸리티를 직접 만들 수 있습니다.