라이브러리 타입 정의를 읽는데 infer 키워드가 이해 안 됐던 이유
문제 발생
라이브러리 타입 정의(.d.ts)를 읽다가 다음과 같은 코드를 만났는데, infer가 뭘 하는 키워드인지 이해가 안 됐습니다.
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;원인 분석
**조건부 타입(conditional type)**은 T extends U ? X : Y 형태로 "타입 T가 U와 호환되면 X, 아니면 Y"를 표현합니다. **infer**는 이 조건부 타입의 extends 절 안에서만 쓸 수 있는 특별한 키워드로, 비교 대상 타입의 특정 부분을 새 타입 변수로 뽑아내겠다는 뜻입니다 — 정규식의 캡처 그룹과 비슷한 역할을 합니다.
T extends Promise<infer U> ? U : T를 풀어서 읽으면: "T가 Promise<무언가> 형태라면, 그 무언가를 U라는 이름으로 뽑아내서 결과 타입으로 쓰고, 아니라면 T를 그대로 쓴다"는 뜻입니다.
type A = UnwrapPromise<Promise<string>>; // string
type B = UnwrapPromise<number>; // number (Promise가 아니므로 그대로)해결 방안
infer는 이미 있는 제네릭 타입의 내부 구조에서 정보를 뽑아내야 할 때 씁니다 — 라이브러리 타입 정의를 직접 작성할 일이 흔치는 않지만, TypeScript 내장 유틸리티 타입들이 실제로 이 패턴으로 만들어져 있어서 읽을 줄 아는 것만으로도 도움이 됩니다.
// TypeScript 내장 유틸리티 타입들의 실제 구현
type ReturnType<T extends (...args: never[]) => unknown> =
T extends (...args: never[]) => infer R ? R : never;
type Parameters<T extends (...args: never[]) => unknown> =
T extends (...args: infer P) => unknown ? P : never;- 배열이나 튜플에서도 같은 방식으로 원소 타입을 뽑아낼 수 있습니다.
type FirstElement<T extends unknown[]> = T extends [infer F, ...unknown[]] ? F : never;
type X = FirstElement<[string, number]>; // string- 직접 이런 조건부 타입을 매번 새로 작성할 필요는 거의 없습니다 —
ReturnType<T>,Parameters<T>,Awaited<T>같은 TypeScript 내장 유틸리티 타입이 이미 흔한 패턴을 다 커버합니다. 대부분의 실무 코드는 이런 내장 유틸리티를 조합해서 쓰는 것으로 충분하고,infer를 직접 쓰는 커스텀 조건부 타입은 라이브러리 타입 정의처럼 정말 필요한 경우에만 작성하는 것이 유지보수 관점에서 낫습니다. - 이해가 어렵다면 TypeScript Playground에서 실제 타입을 여러 입력값으로 실험해보는 것이 문서만 읽는 것보다 훨씬 직관적으로 와닿습니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.