제네릭(Generics)은 타입을 매개변수처럼 받아, 여러 타입에 같은 로직을 재사용하면서도 타입 정보를 잃지 않게 하는 기능이다.
function first<T>(arr: T[]): T { return arr[0] } // <T>: 타입 변수 선언
first<string>(['a']) // <string>: T에 string 대입
first([1, 2]) // 보통은 추론으로 T = number- 타입 매개변수는 반환 타입이 아니다.
<string>은 T에 값을 채울 뿐이고, 반환 타입은 시그니처가 정한다.reduce<Record<string, User[]>>(…)가 결과 타입처럼 보이는 건reduce가 우연히U를 반환하기 때문이다 - any와의 차이:
any[]를 받으면 결과도any라 오류가 숨는다. 제네릭은 호출 시점에 타입이 정해져 검사가 유지된다 - 제약(Generic Constraints,
extends):<T extends { length: number }>처럼 받을 수 있는 범위를 좁힌다.<T, K extends keyof T>(obj: T, key: K): T[K]는 실제 있는 키만 받는다(keyof) - 인스턴스마다 타입을 고정하고 싶으면 제네릭 클래스(
DataStorage<string>), 섞여도 되면 유니언을 쓴다 - React의
useState<number>,useRef<HTMLInputElement>(null)도 제네릭이다
좋은 제네릭 쓰기 (Handbook 지침)
- 타입 매개변수는 제약보다 실제 쓰이는 곳에 둔다:
<T>(arr: T[])가<T extends any[]>(arr: T)보다 추론이 정확하다 - 가능한 한 적게 쓴다
- 한 곳에만 나오는 타입 매개변수는 필요 없을 가능성이 높다
타입을 계산하는 쪽으로 나아가면 조건부 타입과 infer·유틸리티 타입가 된다. 과한 일반화는 YAGNI를 떠올린다.
출처: TypeScript Handbook - Generic Functions · Guidelines for Writing Good Generic Functions