개발새발

infer로 타입스크립트의 추론을 직접 활용하자 본문

Typescript

infer로 타입스크립트의 추론을 직접 활용하자

비숑주인 2025. 4. 2. 19:30

TypeScript는 강력한 타입 시스템을 제공하며, 그 중에서도 infer 키워드는 타입 추론을 명시적으로 제어할 수 있는 수단이다. 특히 조건부 타입(Conditional Types) 과 함께 사용할 때 진가를 발휘한다.

 

infer란 무엇인가?

infer는 타입스크립트의 타입 추론(type inference) 기능을 직접 사용할 수 있도록 해주는 키워드이다. 일반적으로 타입스크립트는 컴파일 시 타입을 추론하지만, 조건부 타입 내부에서 특정 부분의 타입을 추론하고 싶을 때 infer를 사용할 수 있다.

 

배열 요소 타입 추론하기

type El<T> = T extends (infer E)[] ? E : never;

type Str = El<string[]>;             // string
type NumOrBool = El<(number | boolean)[]>; // number | boolean

 

위의 예제에서 infer E는 T가 배열일 경우 그 요소 타입을 E로 추론하게 한다. 추론된 타입은 조건부 타입의 참 부분에서만 사용 가능하다. 다음과 같이 거짓 부분에서 쓰면 오류가 발생한다 

type Invalid<T> = T extends (infer E)[] ? never : E; // ❌ 오류: E는 참 영역에서만 유효

 

함수 타입에서 추론하기

타입스크립트는 함수 타입에 대해 다양한 부분을 추론할 수 있다. 대표적으로 아래와 같은 유틸리티 타입이 있다 

type MyParameters<T> = T extends (...args: infer P) => any ? P : never;
type MyReturnType<T> = T extends (...args: any) => infer R ? R : any;
type MyConstructorParameters<T> = T extends new (...args: infer P) => any ? P : never;
type MyInstanceType<T> = T extends new (...args: any) => infer R ? R : any;
type P = MyParameters<(a: string, b: number) => string>; // [a: string, b: number]
type R = MyReturnType<(a: string, b: number) => string>; // string
type CP = MyConstructorParameters<new (x: string, y: number) => {}>; // [x: string, y: number]
type I = MyInstanceType<new (x: string, y: number) => {}>; // {}

 

튜플의 각 요소에 이름이 붙는 것은 편의상 추가된 기능이며, 실질적으로는 [string, number]와 동일하다.

 

타입스크립트 내장 타입과 충돌 주의

아래와 같이 My를 뺀 이름으로 선언하면 충돌이 발생할 수 있다 

type Parameters<T> = T extends (...args: infer P) => any ? P : never; // ❌ 중복 선언 오류

 

이유는 Parameters, ReturnType 등의 타입은 이미 TypeScript의 표준 라이브러리 lib.es5.d.ts에 정의되어 있기 때문이다.

 

타입 변수 여러 개 추론하기

 

조건부 타입에서 다중 타입 변수도 추론할 수 있다

type MyPAndR<T> = T extends (...args: infer P) => infer R ? [P, R] : never;

type PR = MyPAndR<(a: number, b: string) => string>; // [[a: number, b: string], string]

 

같은 타입 변수의 유니언/인터섹션

타입스크립트는 같은 이름의 타입 변수를 여러 위치에서 사용하면 특별한 동작을 한다.

 

유니언 만들기 

type Union<T> = T extends { a: infer U, b: infer U } ? U : never;

type Result1 = Union<{ a: 1 | 2, b: 2 | 3 }>; // 1 | 2 | 3

 

 

U는 두 위치에 공통적으로 사용되었기 때문에, 타입스크립트는 자동으로 유니언을 생성한다.

 

인터섹션 만들기 (매개변수 위치)

type Intersection<T> = T extends {
  a: (pa: infer U) => void;
  b: (pb: infer U) => void;
} ? U : never;

type Result2 = Intersection<{
  a: (pa: 1 | 2) => void;
  b: (pb: 2 | 3) => void;
}>; // 2

 

 

함수 매개변수는 반공변성(contravariant) 을 가지므로, 같은 이름의 타입 변수는 인터섹션이 된다.

 

매개변수와 반환값에 동시에 사용한 경우

type ReturnAndParam<T> = T extends {
  a: () => infer U;
  b: (p: infer U) => void;
} ? U : never;

type Result3 = ReturnAndParam<{
  a: () => 1 | 2;
  b: (p: 1 | 2 | 3) => void;
}>; // 1 | 2

type Result4 = ReturnAndParam<{
  a: () => 1 | 2;
  b: (p: 2 | 3) => void;
}>; // never

 

 

반환 타입이 매개변수 타입의 부분집합일 때만 교집합 추론이 가능하다.

그렇지 않으면 never로 추론된다.

 

고급 기법: 유니언 → 인터섹션 변환

아래는 UnionToIntersection이라는 고급 타입 유틸리티이다 

type UnionToIntersection<U> =
  (U extends any ? (p: U) => void : never) extends (p: infer I) => void
    ? I
    : never;

type Result5 = UnionToIntersection<{ a: number } | { b: string }>; // { a: number } & { b: string }

 

 

조건부 타입의 분배 법칙을 활용하여, 유니언을 각각 함수의 매개변수로 바꾼 후, 추론된 매개변수 타입 I를 인터섹션으로 만든다.


기능 설명
infer 타입스크립트의 추론 결과를 타입 변수로 캡처
Parameters<T> 함수의 매개변수 타입 추론
ReturnType<T> 함수의 반환 타입 추론
InstanceType<T> 생성자의 인스턴스 타입 추론
여러 타입 변수 동시에 여러 타입을 추론하거나 하나의 타입을 여러 위치에 활용 가능
유니언/인터섹션 처리 같은 타입 변수지만 위치(매개변수/리턴)에 따라 다르게 동작
고급 유틸 유니언 → 인터섹션 등 타입 수준의 변형 가능

 

infer는 처음에는 복잡해 보일 수 있지만, 구조를 파악하고 다양한 예제를 접하면 타입스크립트의 타입 시스템을 더 깊이 이해하고, 정교한 타입 유틸리티를 만들 수 있게 해준다.