TS에서 infer 마스터하기
T0 배열 요소 타입과 T1 함수의 반환 값 타입을 어떻게 계산하는지 알고 계신가요?
잠시 생각해보세요.
type T0 = string[]; type T1 = () => string;
우리는 타입스크립트에서 제공하는 타입 패턴 매칭 기술을 사용할 수 있는데요, 조건부 타입 (Conditional Type) 과 Infer 기능을 사용해 질문에 대한 답을 명확히 할 수 있습니다.
조건부 타입은 두 타입 간의 관계를 탐지할 수 있게 해주며, 두 타입의 호환성 여부를 확인할 수 있습니다.
Infer는 패턴 매칭 중 캡쳐된 타입을 저장하기 위해 타입 변수를 선언하는 데 사용됩니다.
그렇다면, T0 배열의 요소 타입을 어떻게 캡쳐하는지 알아봅니다.
type UnpackedArray

- T extends (infer U)[] ? U : T
→ 위 코드 중,T extends (infer U)[] ? U : T문법은 조건부 타입을 의미합니다.
- infer U
→ 이 중, extends 문 안의infer U은 추론된 타입을 저장하기 위해 새로운 타입 변수 U를 도입한 부분입니다.
더 깊은 이해를 위해, UnpackedArray utility 타입 유행의 실행 흐름을 보여드리겠습니다.
화면 기록 2023-01-16 오전 10.38.19.mov

유의해야할 점은, infer 는 항상 조건부 타입의 extends 문 안에서만 사용될 수 있다는 점입니다.
그리고 infer에 의해 선언된 타입 변수는 오직 조건부 타입의 true 브랜치(조건값이 true일 때 실행되는 구문) 에서만 사용될 수 있습니다.
아래는 infer 사용을 잘못한 예시입니다.
type Wrong1<T extends (infer U)[]> = T[0] // Error type Wrong2

위의 지식들을 사용하지 않고 T1 함수 반환 타입을 가져오는 방법을 알아보겠습니다.
type UnpackedFn
UnpackedFn 유틸리티 타입의 구현을 살펴보면, 꽤 간단해 보이지 않나요?
함수 과부하 시나리오(function overloading scenarios)가 일어나면, 타입스크립트는 타입 추론을 위해 last call signature을 사용할 것입니다. (?)
- 함수 과부하 시나리오(function overloading scenarios)?? ** last call signature

만약 타입스크립트의 조건부 타입에 대해 친숙하지 안다면 아래 글을 읽어보세요.
위 글에서, 우리가 더 강력한 unpacked 유틸리티 타입을 사용할 수 있게 해주는 조건부 체이닝을 소개하고 있습니다.
type Unpacked
위 코드에서, unpacked 유틸리티 타입은 아래 경우의 타입들을 추론하기 쉽게 해주기 위해 조건부 타입과 조건부 체인을 사용합니다.
배열 요소의 타입 추론
함수의 반환 값 타입 추론
Promise 유형의 반환 값 타입 추론
실제로 조건부 타입과 infer을 사용하여 우리는 객체의 각 키에 해당하는 값의 타입을 추론할 수 있습니다.
다음으로 구체적인 예제를 살펴봅시다.
type User = { id: number; name: string; } type PropertyType
PropertyType 유틸리티 타입에서, 우리는 infer를 사용하여 두 개의 타입 변수 U, R을 선언합니다.
이 때 U와 R은 객체에서 각 id 프로퍼티와 name 프로퍼티의 타입을 의미합니다.
만약 타입이 일치하면, 우리는 id 프로퍼티와 name 프로퍼티의 타입을 튜플로 반환합니다.
그러면 이제 질문이 하나 떠오릅니다. 만약 PropertyType 유틸리티 타입에 오직 U 타입 변수 하나만 사용된다면 결과는 어떻게 나올까요? 확인해봅시다.
type PropertyType
위 코드에서 확인할 수 있듯이, U4 타입은 string과 number의 union 타입을 반환합니다. 왜 이런 결과가 나왔을까요? 이유는 만약 covariant 상황에서 동일한 타입 변수에 여러 개의 타입 후보들이 있다면 마지막 타입은 union 타입으로 추론되기 때문입니다.
- covariant : 부모 객체의 타입을 상속받는 상황
하지만 반대의 상황에서, 동일한 타입 변수에 여러 개의 타입 후보들이 있을 경우, 마지막 타입은 교차(intersection) 타입으로 추론됩니다. 구체적으로 증명해봅시다.
type Bar
위 코드에서, U5 타입은 string과 number 타입으로 이루어진 교차 타입을 반환합니다. 즉 최종 타입이 never 타입이 아닙니다.
마지막으로, 타입스크립트 4.7에서 소개된 infer 타입 추론을 보다 간결하게 만드는 새로운 infer관련 기능들을 살펴봅시다. infer와 관련된 새로운 기능을 살펴보기 전에, 예제를 봅시다.
type FirstIfString
위 코드에서 FirstIfString 유틸리티 타입은 타입스크립트의 조건부 타입, 조건부 체인과 infer 타입 추론을 사용하였습니다. 첫 번째 조건문에서 우리는 타입 변수 T가 비어 있지 않은 튜플 타입인지 아닌지를 판단하고, 패턴 매칭 중 캡쳐된 튜플의 첫 번째 요소 타입을 저장하기 위해 infer를 사용하여 타입 변수 S를 선언하였습니다.
두 번째 조건문에서 계속해서 타입 변수 S가 스트링 타입의 subtype인지 아닌지(string 타입인지 아닌지)를 판단하고 조건문이 충족되면 S 타입을 반환하고, 그렇지 않다면 모든 false 브랜치 (조건문이 false일 때 실행되는 구문) 에서 never 타입을 반환합니다.
FirstIfString 유틸리티 타입이 무엇을 하는지 소개한 이후, 기능에 대해 설명합니다.

위의 결과 이미지에서 보이는 것처럼, FirstIfString 유틸리티 타입은 제대로 동작하는 것 같습니다.
그렇다면 질문 하나가 생각나는데요, 유틸리티 유형은 내부적으로 두 가지 조건부 유형을 사용하고 있기 때문에 우라는 위와 같은 동일한 기능을 얻기 위해 단 하나의 조건문만 사용할 수 있을까요?
타입스크립트 4.7은 infer 타입에 옵션으로 extends 문을 추가하여 타입 변수에 대한 명시적 제약 조건을 지정할 수 있게 합니다.
type FirstIfString
이전의 코드와 위 코드를 비교하면, 더 코드가 깔끔해지지 않았나요?
이 글을 읽은 후, 당신이 조건부 타입과 infer의 목적에 대해 명확히 이해했을 것이라 믿습니다.
그렇다면, 이제 아래 코드에서 사용된 UnionToIntersection 유틸리티 타입의 구체적인 구현을 이해할 수 있겠습니까?
type UnionToIntersection = ( U extends any ? (arg: U) => void : never ) extends (arg: infer R) => void ? R : never
