TypeScript 5.5의 infer를 이용한 타입 추론 패턴
문제 상황
레거시 API가 중첩된 응답 구조를 반환하는데, 실제 데이터는 data.result.items 깊숙이 있었다. 매번 타입을 수동으로 파싱하기보다 유틸리티 타입으로 자동 추출하고 싶었다.
type ApiResponse<T> = {
data: {
result: {
items: T[];
};
};
};
infer를 이용한 추출
조건부 타입과 infer를 조합하면 제네릭 타입 내부의 특정 타입을 추출할 수 있다.
type ExtractItems<T> = T extends ApiResponse<infer U> ? U : never;
type UserResponse = ApiResponse<{ id: number; name: string }>;
type User = ExtractItems<UserResponse>; // { id: number; name: string }
실무 적용
여러 API 엔드포인트에서 공통적으로 사용할 수 있는 유틸리티 타입들을 만들었다.
// Promise에서 resolve 타입 추출
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
// 배열에서 요소 타입 추출
type ArrayElement<T> = T extends (infer U)[] ? U : never;
// 함수 반환 타입 추출
type ReturnTypeOf<T> = T extends (...args: any[]) => infer R ? R : never;
특히 UnwrapPromise는 async 함수의 반환 타입을 다룰 때 유용했다. Awaited<T> 유틸리티 타입이 표준으로 있지만, 커스텀 래퍼가 필요한 경우 infer 패턴을 알아두면 응용할 수 있다.
주의사항
과도하게 복잡한 타입 추론은 오히려 가독성을 해친다. 타입이 3단계 이상 중첩되면 명시적으로 선언하는 게 나았다. 또한 infer는 조건부 타입 내에서만 사용 가능하므로 문맥을 잘 이해해야 한다.
타입 시스템을 잘 활용하면 런타임 에러를 줄이고 리팩토링이 훨씬 안전해진다. 다만 타입을 위한 타입은 지양하고, 실제 비즈니스 로직에 도움이 되는 선에서 사용하는 게 중요하다.