TypeScript의 infer 키워드로 복합 타입에서 원하는 부분만 깔끔하게 추출하기
안녕하세요. 오늘은 TypeScript의 조건부 타입(Conditional types)에서 등장하는 infer라는 키워드에 대해 자세히 알아볼까 합니다.
이 infer 키워드를 활용하면 다소 복잡한 복합(Compound) 타입 안에서 원하는 부분의 타입만 쏙쏙 뽑아서 깔끔하게 사용할 수가 있는데요.
이 글에서 사용하는 표기법
이 글에서는 타입의 정확성이나 추론된 타입을 간편하게 확인하기 위해 asserttt라는 npm 패키지를 사용한 코드 예제를 활용할 건데요.
예를 들면 이런 식입니다.
// 값의 타입 확인
assertType<string>('abc');
assertType<number>(123);
// 타입의 동등성 확인
type Pair<T> = [T, T];
type _ = Assert<Equal<
Pair<string>, [string, string]
>>;
infer 키워드란? 조건부 타입의 extends 절에서 타입 추출하기
infer 키워드는 조건부 타입(Conditional types)의 extends 절 내에서 타입을 추출할 때 쓰는 키워드입니다.
조건부 타입은 보통 이런 형태를 갖는데요.
type _ = Type extends Constraint ? <then> : <else>;
이때 Constraint(제약 조건)는 하나의 타입 패턴(Type Pattern)일 수 있습니다. 예를 들면 다음과 같습니다.
T[]
Promise<T>
(arg: T) => R
기존 타입 변수(위 예시의 T나 R)를 사용할 수 있는 위치라면 어디든지 infer를 이용해 새로운 타입 변수를 선언할 수 있는데요.
예를 들어 배열 타입에서 배열 내부의 요소 타입을 추출하는 경우를 살펴보겠습니다.
type ElemType<Arr> = Arr extends Array<infer Elem> ? Elem : never;
// 실제 타입 추출 예시
type _ = Assert<Equal<
ElemType<Array<string>>, string
>>;
이런 타입 추출 방식은 JavaScript의 구조분해 할당(Destructuring)과 비슷한 느낌을 줍니다.
예시: Record 타입을 이용해 객체의 키와 값을 추출하기
내장 유틸리티 타입인 Record를 이용하면 직접 키(keyof)나 값(ValueOf)을 추출하는 타입을 구현할 수도 있습니다.
const Color = {
red: 0,
green: 1,
blue: 2,
} as const;
// 키 추출 타입
type KeyOf<T> = T extends Record<infer K, any> ? K : never;
type _1 = Assert<Equal<
KeyOf<typeof Color>,
"red" | "green" | "blue"
>>;
// 값 추출 타입
type ValueOf<T> = T extends Record<any, infer V> ? V : never;
type _2 = Assert<Equal<
ValueOf<typeof Color>,
0 | 1 | 2
>>;
infer를 쓰는 TypeScript 내장 유틸리티 타입들 알아보기
TypeScript는 이미 여러 내장 유틸리티 타입을 제공합니다.
이 중에서도 infer를 사용하는 유틸리티 타입 몇 가지를 살펴보겠습니다.
함수 타입에서 infer로 타입 추출하기
// 함수의 매개변수 추출
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never;
// 함수의 반환 타입 추출
type ReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : any;
실제 활용 예시를 들어보겠습니다.
function add(x: number, y: number): number {
return x + y;
}
type _1 = Assert<Equal<
typeof add,
(x: number, y: number) => number
>>;
type _2 = Assert<Equal<
Parameters<typeof add>,
[x: number, y: number]
>>;
type _3 = Assert<Equal<
ReturnType<typeof add>,
number
>>;
클래스 타입에서 infer로 타입 추출하기
클래스 타입의 생성자 파라미터와 인스턴스 타입을 추출할 때도 infer를 활용할 수 있습니다.
type Class<T> = abstract new (...args: Array<any>) => T;
// 생성자 파라미터 타입 추출
type ConstructorParameters<T extends abstract new (...args: any) => any> =
T extends abstract new (...args: infer P) => any ? P : never;
// 인스턴스 타입 추출
type InstanceType<T extends abstract new (...args: any) => any> =
T extends abstract new (...args: any) => infer R ? R : any;
예시로 클래스 Point를 살펴볼까요?
class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
const pointAsValue: Class<Point> = Point;
type _ = [
Assert<Equal<
ConstructorParameters<typeof Point>,
[x: number, y: number]
>>,
Assert<Equal<
InstanceType<typeof Point>,
Point
>>,
];
비동기 메서드를 동기 메서드로 바꾸는 예시 (고급 활용)
좀 더 복잡한 사례로, 비동기 메서드(Promise
기반)를 동기 메서드로 바꾸는 유틸리티 타입도 만들 수 있습니다.
type Syncify<Intf> = {
[K in keyof Intf]:
Intf[K] extends (...args: infer A) => Promise<infer R>
? (...args: A) => R
: Intf[K]
};
interface AsyncService {
factorize(num: number): Promise<Array<number>>;
createDigest(text: string): Promise<string>;
}
type SyncService = Syncify<AsyncService>;
type _ = Assert<Equal<
SyncService,
{
factorize: (num: number) => Array<number>,
createDigest: (text: string) => string,
}
>>;
복잡한 타입을 infer로 간단히 별칭(alias) 만들기
복잡한 타입을 만들 때, infer를 통해 일종의 타입 변수처럼 간편하게 별칭을 만들 수도 있습니다.
type WrapTriple<T> = Promise<T> extends infer W
? [W, W, W]
: never;
type _ = Assert<Equal<
WrapTriple<number>,
[Promise<number>, Promise<number>, Promise<number>]
>>;
'Javascript' 카테고리의 다른 글
TypeScript 조건부 타입 완벽 가이드: 유니온 타입과 유틸리티 타입 활용의 모든 것 (0) | 2025.03.15 |
---|---|
TypeScript Mapped Types 완벽 정복: 기본부터 고급 활용까지 (0) | 2025.03.13 |
TypeScript satisfies 연산자 완벽 정리: 타입 체크의 새로운 강자 (0) | 2025.03.03 |
TypeScript에서 읽기 전용 속성 완벽 정리: readonly 키워드 활용법 (0) | 2025.03.03 |
TypeScript로 구현하는 최신 ESM 기반 npm 패키지 퍼블리싱 가이드 (0) | 2025.03.03 |