
Advanced TypeScript | January 23, 2023
TypeScript 의 Infer 에 대하여
객체나 배열과 같은 복잡한 타입에서 어떻게 특정한 부분의 타입만을 추론할 수 있는가?
Infer...?
infer 는 조건부 타입에서 조건식이 참으로 평가될 때, 비교식에서 대응되는 특정 타입을 추론해 내는 기능입니다.
🤷🏻♂️: 아니 이게 뭔 뚱딴지 같은 소립니까? 조건부 타입은 또 뭔데요?
라고 생각하실 수 있는 여러분을 위해, 조건부 타입에 대해 간단히 알아 본 후 예제와 함께 Infer 에 대해 알아 봅시다.
Conditional Types
조건부 타입은 특정 타입이 비교 대상 타입에 할당 될 수 있는지 비교하고, 참/거짓 분기로 타입을 반환할 수 있습니다.
Typescript
1T extends U ? X : Y기본 형태는 위와 같습니다. 특정 타입(T)이 비교 대상 타입(U)에 할당될 수 있는지 비교하고 참일 경우 X 타입을, 거짓일 경우 Y 타입을 반환합니다.
실제 사용 케이스를 예로 들어봅시다. 만약 어떤 타입이 올지 모르는 제네릭 타입이 배열 타입에 할당될 수 없다면, never 타입을 반환하는 타입을 어떻게 구현할 수 있을까요?
Typescript
1type IsArray<T> = T extends unknown[] ? T : never;위 처럼 조건부 타입을 사용해서, 제네릭 타입이 배열 타입에 할당될 수 있는지 비교하고, 할당될 수 있으면 그 타입을 반환하고, 그렇지 않은 경우 never 타입을 반환하도록 구현하면 됩니다.
Typescript
1type A = IsArray<number[]>; // 배열 타입에 할당 가능하므로 number[]
2type B = IsArray<number>; // 배열 타입에 할당할 수 없으므로 never결과는 아시다시피 위와 같습니다.
오예. 우리는 이제 조건부 타입을 어느정도 알게되었습니다.

여기서 억지스럽지만 한 가지 요구사항을 바꿔봅시다. 만약 배열 타입에 할당 가능할 경우 배열의 요소에 대한 타입을 반환할 수는 없을까요?
Typescript
1type NumberArrayElement<T> = T extends number[] ? number : never;number[] 타입일 경우에 number 를 반환하도록 구현해 봤습니다. 사실상 number[] 타입만이 할당될 수 있으므로 아닌 경우 무조건 never 를 반환할 것입니다.
🤷🏻♂️: 아니 그러면 다른 배열 요소 타입을 반환하고 싶으면 어떡하란 말입니까?
그럴 때 우리는 infer를 사용할 수 있습니다.
Infer!
자, 조건부 타입까지는 알았으니 예제를 통해 infer 에 대해 알아봅시다.
Typescript
1T extends infer U ? U : T;기본적인 사용법은 위와 같습니다. 특정 타입(T)이 비교 대상 타입(U)에 할당될 수 있는지 비교하고 참일 경우 비교 대상 타입(U)을 반환하고, 그렇지 않으면 특정 타입(T)을 반환합니다.
제가 조건부 타입에 대해 말씀드린 이유가 있습니다. infer 는 조건부 타입 구문에서만 사용할 수 있습니다. 또한 조건이 참일 경우 반드시 infer 를 통해 생성한 타입을 포함하고 있는 타입을 반환해야 합니다.
어라 그런데 생긴게 조건부 타입과 별반 다를 게 없어 보이죠? 사실 위의 예제처럼 사용하면 아무런 의미가 없습니다. 설명을 조금 더 드린 뒤 실제 사용 케이스와 함께 보시죠.
Infer 는 타입을 추론한다?
infer 는 조건부 타입 구문에서 비교식이 참인 경우, 비교식에서 대응되는 타입을 추론해 냅니다.
Typescript
1T extends infer U ? U : T;기본적인 사용법으로 보여드린 위의 예제에서의 비교식이 참인 경우, U 타입은 T 타입과 대응합니다. 따라서 U 타입은 자연스레 T 타입으로 추론이 가능한 것이죠.
아직 잘 감이 오지 않죠? 실제 사용 케이스를 예시로 들어봅시다.
아까 조건부 타입에 대한 예시 설명의 마지막 내용에서 이런 의문을 가졌었죠?
🤷🏻♂️: 아니 그러면 다른 배열 요소 타입을 반환하고 싶으면 어떡하란 말입니까?
특정 타입이 비교 대상 타입에 할당될 수 있으면 비교식에서 대응되는 배열의 요소에 대해 추론된 타입을 반환 하려면 어떻게 타입을 구현해야 할까요?
Typescript
1type ArrayElement<T> = T extends (infer U)[] ? U : never;위와 같이 구현할 수 있겠습니다. 비교식이 참이라면 T 타입은 배열 타입일 테니, 비교식에서 infer U 에 대응되는 부분은 배열의 요소이므로 T 타입의 배열 요소에 해당하는 추론된 U 타입을 반환하도록 하면 되겠네요!
Typescript
1type A = ArrayElement<number[]>; // number
2type B = ArrayElement<string[]>; // string
3type C = ArrayElement<boolean>; // never결과는 다음과 같습니다. C 타입의 경우 boolean 타입은 배열 타입에 할당될 수 없으므로 위의 조건부 타입에 의해 never 타입이 반환됩니다.
Infer 가 추론하고자 하는 타입이 여러 개라면?
infer 는 비교식에 대응되는 타입을 추론한다는 사실을 알았습니다. 그럼 만약 infer 를 사용한 동일한 추론 가능한 타입을 중복하여 사용한다면, 즉 추론하고자 하는 타입이 여러 개라면 어떨까요?
Typescript
1type UserProperties<T> = T extends { id: infer U, name: infer U } ? U : never;Typescript
1type User = { id: string, name: number };
2
3type A = UserProperties<User>;위의 예제의 UserProperties 타입에서 T 객체 타입의 id 와 name 프로퍼티에 대응되는 추론 가능한 타입이 여러 개인 경우, 추론 가능한 모든 타입의 합집합인, 즉 유니온 타입이 반환됩니다.
Typescript
1type A = UserProperties<User>; // string | numberExamples
자, 이제 infer 에 대해 알았습니다. 완벽한 이해를 위해 예제를 좀 더 볼까요?
TypeScript 에는 이미 구현된 많은 유틸리티 타입들이 존재합니다. 이 유틸리티 타입들은 infer 를 사용하여 구현하기도 합니다.
본 글의 주제인 infer 를 사용하여 만든 유틸리티 타입을 한번 봅시다.
Typescript
1type A = Parameters<(x: number) => string>; // [x: number]
2type B = ReturnType<(x: number) => string>; // stringParameters 타입은 함수 타입을 전달하면 함수 타입의 파라미터에 대응되는 추론된 타입을 반환하고, ReturnType 타입은 반환 값에 대응되는 추론된 타입을 반환합니다.
오우. 이제는 어떻게 구현되어 있을지 예상이 갑니다.
Typescript
1type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : any;
2type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;T 타입이 함수 타입에 할당할 수 있으면, T 타입은 함수 타입입니다. 조건식에서 대응되어야 할 부분은 함수의 파라미터 타입이므로, 파라미터 타입과 반환 값의 타입에 infer 를 사용해 추론할 P 와 R 타입이 대응되도록 하고, 참일 경우 반환하도록 합니다.