TypeScript를 사용하다 보면 더욱 정교한 타입 시스템의 필요성을 느끼게 됩니다. 특히, 복잡한 제네릭 타입을 다룰 때는 조건부 타입(Conditional Types)과 함께 infer 키워드가 매우 유용하게 사용될 수 있습니다.
이 글에서는 TypeScript의 infer 키워드가 무엇인지, 그리고 어떻게 활용할 수 있는지에 대해 살펴보겠습니다.
infer는 TypeScript의 조건부 타입에서 사용되는 키워드로, 특정 타입의 일부를 추론할 수 있도록 도와줍니다. infer는 제네릭 타입을 분석하여 새로운 타입을 추출하는 역할을 합니다.
조건부 타입의 extends 절 내에서만 사용할 수 있으며, 타입 패턴 매칭을 통해 원하는 부분의 타입을 추출합니다.
먼저, infer의 기본적인 사용법을 살펴보겠습니다. 여기서는 함수의 반환 타입을 추론하는 예제를 사용합니다.
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;위 코드에서 GetReturnType 타입은 제네릭 T가 함수 타입인 경우, 그 함수의 반환 타입을 추론하여 R로 설정합니다. 만약 함수 타입이 아니라면 never를 반환합니다.
이 타입을 사용하면 특정 함수의 반환 타입을 자동으로 추론할 수 있습니다.
function greet(): string {
return "Hello";
}
type GreetReturn = GetReturnType<typeof greet>; // string이제 infer 키워드를 실전에서 어떻게 활용할 수 있는지 몇 가지 예제를 통해 살펴보겠습니다.
infer를 사용하면 함수의 인수 타입을 추출할 수 있습니다.
type GetArguments<T> = T extends (...args: infer A) => any ? A : never;
type Args1 = GetArguments<(x: number, y: string) => void>; // [number, string]
type Args2 = GetArguments<() => void>; // []이 코드에서 GetArguments 타입은 주어진 함수의 인수 타입을 튜플로 반환합니다. 이를 통해 함수의 인수 타입을 쉽게 추출할 수 있습니다.
infer는 프로미스 타입의 반환 값을 추론하는 데도 매우 유용합니다.
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type Example1 = UnwrapPromise<Promise<string>>; // string
type Example2 = UnwrapPromise<number>; // numberUnwrapPromise 타입은 프로미스에서 반환되는 실제 타입을 추론합니다. 프로미스 타입이 아닌 경우에는 원래 타입을 그대로 반환합니다.
이는 async/await와 함께 사용될 때 특히 유용합니다:
async function fetchUser(): Promise<{ name: string; age: number }> {
// ...
}
type User = UnwrapPromise<ReturnType<typeof fetchUser>>;
// { name: string; age: number }배열 타입에서 요소의 타입을 추출하는 방법도 infer로 간단하게 구현할 수 있습니다.
type ElementType<T> = T extends (infer U)[] ? U : T;
type Example1 = ElementType<string[]>; // string
type Example2 = ElementType<number[]>; // number
type Example3 = ElementType<boolean>; // booleanElementType 타입은 배열의 요소 타입을 반환하며, 배열이 아닌 타입이 주어질 경우 그 타입 자체를 반환합니다.
튜플 타입에서 첫 번째 요소의 타입을 추출할 때도 infer를 사용할 수 있습니다.
type First<T> = T extends [infer U, ...any[]] ? U : never;
type Example1 = First<[string, number, boolean]>; // string
type Example2 = First<[boolean, number]>; // booleanFirst 타입은 튜플의 첫 번째 요소 타입을 추출하여 유용하게 활용할 수 있습니다.
마지막 요소를 추출하는 것도 가능합니다:
type Last<T> = T extends [...any[], infer U] ? U : never;
type Example1 = Last<[string, number, boolean]>; // booleaninfer를 중첩하여 더 복잡한 타입 추론도 가능합니다:
type DeepUnwrap<T> = T extends Promise<infer U>
? DeepUnwrap<U>
: T;
type Example = DeepUnwrap<Promise<Promise<Promise<string>>>>; // stringinfer는 여러 조건부 타입과 결합하여 강력한 타입 유틸리티를 만들 수 있습니다:
type FlattenArray<T> = T extends (infer U)[]
? U extends (infer V)[]
? V
: U
: T;
type Example1 = FlattenArray<string[][]>; // string
type Example2 = FlattenArray<number[]>; // numberTypeScript의 infer 키워드는 조건부 타입과 함께 사용하여 복잡한 타입 추론을 가능하게 합니다. 함수의 반환 타입, 인수 타입, 프로미스의 언래핑, 배열 요소 타입 추출 등 다양한 상황에서 활용할 수 있습니다.
infer를 효과적으로 활용하면 타입 안전성을 유지하면서도 유연한 타입 시스템을 구축할 수 있습니다. 특히 라이브러리나 프레임워크를 개발할 때, infer를 사용하면 사용자에게 더 나은 타입 추론 경험을 제공할 수 있습니다.
TypeScript의 고급 타입 시스템과 제네릭을 활용한 확장 가능한 팩토리 패턴 설계 가이드
Enum, Const Enum, 객체 리터럴의 컴파일 동작 비교와 최적의 선택 가이드