개발새발
infer로 타입스크립트의 추론을 직접 활용하자 본문
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는 처음에는 복잡해 보일 수 있지만, 구조를 파악하고 다양한 예제를 접하면 타입스크립트의 타입 시스템을 더 깊이 이해하고, 정교한 타입 유틸리티를 만들 수 있게 해준다.
'Typescript' 카테고리의 다른 글
자기 자신을 타입으로 사용하는 재귀 타입이 있다 (0) | 2025.04.02 |
---|---|
타입을 좁혀 정확한 타입을 얻어내자 (0) | 2025.04.02 |
enum은 자바스크립트에서도 사용할 수 있다 (0) | 2025.04.02 |
클래스는 값이면서 타입이다 (0) | 2025.03.29 |
공변성과 반공변성을 알아야 함수끼리 대입 할 수 있다 (0) | 2025.03.29 |