TypeScript 5.4의 NoInfer 유틸리티 타입 활용기
문제 상황
회사 프로젝트에서 옵션 객체를 받는 제네릭 함수를 만들다가 타입 추론이 의도와 다르게 동작하는 문제를 겪었다.
function createConfig<T extends string>(options: {
mode: T;
fallback: T;
}) {
return options;
}
const config = createConfig({
mode: 'light',
fallback: 'dark'
});
// T는 'light' | 'dark'로 추론됨 (원하는 것: 'light')
mode를 기준으로 타입을 좁히고 싶었지만, TypeScript는 두 프로퍼티 모두에서 타입을 추론해 유니온으로 만들어버렸다.
NoInfer 적용
TypeScript 5.4에서 추가된 NoInfer<T> 유틸리티 타입을 사용하면 특정 위치에서 타입 추론을 제외할 수 있다.
function createConfig<T extends string>(options: {
mode: T;
fallback: NoInfer<T>;
}) {
return options;
}
const config = createConfig({
mode: 'light',
fallback: 'dark' // 에러: 'dark'는 'light' 타입에 할당 불가
});
fallback을 NoInfer<T>로 감싸자 mode에서만 타입이 추론되고, fallback은 그 타입을 따르게 되었다.
실전 활용 사례
프로젝트의 이벤트 핸들러 등록 함수에 적용했다.
type EventMap = {
click: { x: number; y: number };
submit: { formData: FormData };
};
function addEventListener<K extends keyof EventMap>(
event: K,
handler: (data: NoInfer<EventMap[K]>) => void
) {
// 구현
}
addEventListener('click', (data) => {
console.log(data.x, data.y); // 타입 안전
});
handler의 파라미터에서 타입이 역추론되는 것을 방지하여 event 문자열 기반으로만 타입이 결정되도록 했다.
결론
NoInfer는 제네릭 함수에서 타입 추론 방향을 명확히 제어할 수 있게 해준다. 특히 여러 곳에서 같은 제네릭 타입을 사용할 때 의도치 않은 타입 확장을 방지하는 데 유용했다. 5.4 업그레이드 후 기존 타입 헬퍼 함수 몇 개를 리팩토링할 수 있었다.