posts

TS에서 infer 마스터하기

Apr 23, 2026 updated Apr 23, 2026 typescript

T0 배열 요소 타입과 T1 함수의 반환 값 타입을 어떻게 계산하는지 알고 계신가요?

잠시 생각해보세요.

type T0 = string[]; type T1 = () => string;

우리는 타입스크립트에서 제공하는 타입 패턴 매칭 기술을 사용할 수 있는데요, 조건부 타입 (Conditional Type) 과 Infer 기능을 사용해 질문에 대한 답을 명확히 할 수 있습니다.

조건부 타입은 두 타입 간의 관계를 탐지할 수 있게 해주며, 두 타입의 호환성 여부를 확인할 수 있습니다.

Infer는 패턴 매칭 중 캡쳐된 타입을 저장하기 위해 타입 변수를 선언하는 데 사용됩니다.

그렇다면, T0 배열의 요소 타입을 어떻게 캡쳐하는지 알아봅니다.

type UnpackedArray = T extends (infer U)[] ? U : T type U0 = UnpackedArray // string

TS에서 infer 마스터하기 이미지 1

  1. T extends (infer U)[] ? U : T

→ 위 코드 중,T extends (infer U)[] ? U : T문법은 조건부 타입을 의미합니다.

  1. infer U

→ 이 중, extends 문 안의infer U은 추론된 타입을 저장하기 위해 새로운 타입 변수 U를 도입한 부분입니다.

더 깊은 이해를 위해, UnpackedArray utility 타입 유행의 실행 흐름을 보여드리겠습니다.

화면 기록 2023-01-16 오전 10.38.19.mov

TS에서 infer 마스터하기 이미지 2

유의해야할 점은, infer 는 항상 조건부 타입의 extends 문 안에서만 사용될 수 있다는 점입니다.

그리고 infer에 의해 선언된 타입 변수는 오직 조건부 타입의 true 브랜치(조건값이 true일 때 실행되는 구문) 에서만 사용될 수 있습니다.

아래는 infer 사용을 잘못한 예시입니다.

type Wrong1<T extends (infer U)[]> = T[0] // Error type Wrong2 = (infer U)[] extends T ? U : T // Error type Wrong3 = T extends (infer U)[] ? T : U // Error

TS에서 infer 마스터하기 이미지 3

위의 지식들을 사용하지 않고 T1 함수 반환 타입을 가져오는 방법을 알아보겠습니다.

type UnpackedFn = T extends (...args: any[]) => infer U ? U : T; type U1 = UnpackedFn; // string

UnpackedFn 유틸리티 타입의 구현을 살펴보면, 꽤 간단해 보이지 않나요?

함수 과부하 시나리오(function overloading scenarios)가 일어나면, 타입스크립트는 타입 추론을 위해 last call signature을 사용할 것입니다. (?)

  • 함수 과부하 시나리오(function overloading scenarios)?? ** last call signature

TS에서 infer 마스터하기 이미지 4

만약 타입스크립트의 조건부 타입에 대해 친숙하지 안다면 아래 글을 읽어보세요.

위 글에서, 우리가 더 강력한 unpacked 유틸리티 타입을 사용할 수 있게 해주는 조건부 체이닝을 소개하고 있습니다.

type Unpacked = T extends (infer U)[] ? U : T extends (...args: any[]) => infer U ? U : T extends Promise ? U : T; type T0 = Unpacked; // string type T1 = Unpacked<string[]>; // string type T2 = Unpacked<() => string>; // string type T3 = Unpacked<Promise>; // string type T4 = Unpacked<Promise[]>; // Promise type T5 = Unpacked<Unpacked<Promise[]>>; // string

위 코드에서, unpacked 유틸리티 타입은 아래 경우의 타입들을 추론하기 쉽게 해주기 위해 조건부 타입과 조건부 체인을 사용합니다.

배열 요소의 타입 추론

함수의 반환 값 타입 추론

Promise 유형의 반환 값 타입 추론

실제로 조건부 타입과 infer을 사용하여 우리는 객체의 각 키에 해당하는 값의 타입을 추론할 수 있습니다.

다음으로 구체적인 예제를 살펴봅시다.

type User = { id: number; name: string; } type PropertyType = T extends { id: infer U, name: infer R } ? [U, R] : T type U3 = PropertyType // [number, string]

PropertyType 유틸리티 타입에서, 우리는 infer를 사용하여 두 개의 타입 변수 U, R을 선언합니다.

이 때 U와 R은 객체에서 각 id 프로퍼티와 name 프로퍼티의 타입을 의미합니다.

만약 타입이 일치하면, 우리는 id 프로퍼티와 name 프로퍼티의 타입을 튜플로 반환합니다.

그러면 이제 질문이 하나 떠오릅니다. 만약 PropertyType 유틸리티 타입에 오직 U 타입 변수 하나만 사용된다면 결과는 어떻게 나올까요? 확인해봅시다.

type PropertyType = T extends { id: infer U, name: infer U } ? U : T type U4 = PropertyType // string | number

위 코드에서 확인할 수 있듯이, U4 타입은 string과 number의 union 타입을 반환합니다. 왜 이런 결과가 나왔을까요? 이유는 만약 covariant 상황에서 동일한 타입 변수에 여러 개의 타입 후보들이 있다면 마지막 타입은 union 타입으로 추론되기 때문입니다.

  • covariant : 부모 객체의 타입을 상속받는 상황

하지만 반대의 상황에서, 동일한 타입 변수에 여러 개의 타입 후보들이 있을 경우, 마지막 타입은 교차(intersection) 타입으로 추론됩니다. 구체적으로 증명해봅시다.

type Bar = T extends { a: (x: infer U) => void, b: (x: infer U) => void } ? U : never; type U5 = Bar<{ a: (x: string) => void, b: (x: number) => void }>; // string & number

위 코드에서, U5 타입은 string과 number 타입으로 이루어진 교차 타입을 반환합니다. 즉 최종 타입이 never 타입이 아닙니다.

마지막으로, 타입스크립트 4.7에서 소개된 infer 타입 추론을 보다 간결하게 만드는 새로운 infer관련 기능들을 살펴봅시다. infer와 관련된 새로운 기능을 살펴보기 전에, 예제를 봅시다.

type FirstIfString = T extends [infer S, ...unknown[]] ? S extends string ? S : never : never;

위 코드에서 FirstIfString 유틸리티 타입은 타입스크립트의 조건부 타입, 조건부 체인과 infer 타입 추론을 사용하였습니다. 첫 번째 조건문에서 우리는 타입 변수 T가 비어 있지 않은 튜플 타입인지 아닌지를 판단하고, 패턴 매칭 중 캡쳐된 튜플의 첫 번째 요소 타입을 저장하기 위해 infer를 사용하여 타입 변수 S를 선언하였습니다.

두 번째 조건문에서 계속해서 타입 변수 S가 스트링 타입의 subtype인지 아닌지(string 타입인지 아닌지)를 판단하고 조건문이 충족되면 S 타입을 반환하고, 그렇지 않다면 모든 false 브랜치 (조건문이 false일 때 실행되는 구문) 에서 never 타입을 반환합니다.

FirstIfString 유틸리티 타입이 무엇을 하는지 소개한 이후, 기능에 대해 설명합니다.

TS에서 infer 마스터하기 이미지 5

위의 결과 이미지에서 보이는 것처럼, FirstIfString 유틸리티 타입은 제대로 동작하는 것 같습니다.

그렇다면 질문 하나가 생각나는데요, 유틸리티 유형은 내부적으로 두 가지 조건부 유형을 사용하고 있기 때문에 우라는 위와 같은 동일한 기능을 얻기 위해 단 하나의 조건문만 사용할 수 있을까요?

타입스크립트 4.7은 infer 타입에 옵션으로 extends 문을 추가하여 타입 변수에 대한 명시적 제약 조건을 지정할 수 있게 합니다.

type FirstIfString = T extends [infer S extends string, ...unknown[]] ? S : never;

이전의 코드와 위 코드를 비교하면, 더 코드가 깔끔해지지 않았나요?

이 글을 읽은 후, 당신이 조건부 타입과 infer의 목적에 대해 명확히 이해했을 것이라 믿습니다.

그렇다면, 이제 아래 코드에서 사용된 UnionToIntersection 유틸리티 타입의 구체적인 구현을 이해할 수 있겠습니까?

type UnionToIntersection = ( U extends any ? (arg: U) => void : never ) extends (arg: infer R) => void ? R : never

TS에서 infer 마스터하기 이미지 6