본문으로 건너뛰기
개발 머꼬
개발 노트JavaScript
hohyeon.dev28

라이브러리 타입 정의를 읽는데 infer 키워드가 이해 안 됐던 이유

  • #Engineering Note
  • #Generics
  • #TypeScript

문제 발생

라이브러리 타입 정의(.d.ts)를 읽다가 다음과 같은 코드를 만났는데, infer가 뭘 하는 키워드인지 이해가 안 됐습니다.

type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

원인 분석

**조건부 타입(conditional type)**은 T extends U ? X : Y 형태로 "타입 TU와 호환되면 X, 아니면 Y"를 표현합니다. **infer**는 이 조건부 타입의 extends 절 안에서만 쓸 수 있는 특별한 키워드로, 비교 대상 타입의 특정 부분을 새 타입 변수로 뽑아내겠다는 뜻입니다 — 정규식의 캡처 그룹과 비슷한 역할을 합니다.

T extends Promise<infer U> ? U : T를 풀어서 읽으면: "TPromise<무언가> 형태라면, 그 무언가U라는 이름으로 뽑아내서 결과 타입으로 쓰고, 아니라면 T를 그대로 쓴다"는 뜻입니다.

type A = UnwrapPromise<Promise<string>>; // string
type B = UnwrapPromise<number>;          // number (Promise가 아니므로 그대로)

해결 방안

  1. 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;
  1. 배열이나 튜플에서도 같은 방식으로 원소 타입을 뽑아낼 수 있습니다.
type FirstElement<T extends unknown[]> = T extends [infer F, ...unknown[]] ? F : never;
type X = FirstElement<[string, number]>; // string
  1. 직접 이런 조건부 타입을 매번 새로 작성할 필요는 거의 없습니다ReturnType<T>, Parameters<T>, Awaited<T> 같은 TypeScript 내장 유틸리티 타입이 이미 흔한 패턴을 다 커버합니다. 대부분의 실무 코드는 이런 내장 유틸리티를 조합해서 쓰는 것으로 충분하고, infer를 직접 쓰는 커스텀 조건부 타입은 라이브러리 타입 정의처럼 정말 필요한 경우에만 작성하는 것이 유지보수 관점에서 낫습니다.
  2. 이해가 어렵다면 TypeScript Playground에서 실제 타입을 여러 입력값으로 실험해보는 것이 문서만 읽는 것보다 훨씬 직관적으로 와닿습니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.