🔤
TypeScript 제네릭 패턴 — T가 뭔지 한 번에 이해하기
함수/클래스/인터페이스에서 "나중에 결정되는 타입"을 쓰는 법
// any를 쓰면 타입 정보가 사라진다
function first(arr: any[]): any { return arr[0]; }
const x = first([1, 2, 3]); // x의 타입: any 😱
// 제네릭을 쓰면 타입이 보존된다
function first<T>(arr: T[]): T { return arr[0]; }
const x = first([1, 2, 3]); // x의 타입: number ✅
const y = first(['a', 'b']); // y의 타입: string ✅
T는 "호출할 때 결정된다". first([1,2,3])을 호출하면 TypeScript가 T=number로 추론한다.
자주 쓰는 패턴
제약(constraint): <T extends HasId> — T가 HasId 인터페이스를 만족해야 함
interface HasId { id: number; }
function getById<T extends HasId>(items: T[], id: number): T | undefined {
return items.find(item => item.id === id); // item.id 접근 가능 — T가 HasId를 만족하니까
}
keyof: <K extends keyof T> — T의 키 중 하나만 허용
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]; // 반환 타입이 정확히 해당 키의 타입
}
getProperty({ name: 'Kim', age: 30 }, 'name'); // 반환 타입: string
getProperty({ name: 'Kim', age: 30 }, 'age'); // 반환 타입: number
유틸리티 타입: Partial
type Partial<T> = { [K in keyof T]?: T[K] }; // 모든 프로퍼티를 optional로
이 한 줄이 읽히면 TypeScript 제네릭을 이해한 거다.
핵심 포인트
1
T는 "아직 모르는 타입"의 자리표시자 — 호출 시점에 결정
2
T extends Constraint로 제약 추가 — T가 만족해야 하는 조건
3
keyof T로 객체의 키를 타입으로 추출
4
Partial<T>, Pick<T,K> 같은 유틸리티 타입은 제네릭 + mapped type으로 구현
사용 사례
API 클라이언트 — fetch<T>(url): Promise<T>로 응답 타입 자동 추론
컴포넌트 props — React에서 generic component 만들기