본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout

내비게이션

  • Tech
  • Projects
  • Books
  • About
  • Tags

카테고리

  • AI / ML
  • 웹 개발
  • 프로그래밍
  • 개발 도구

연결

  • GitHub
  • Email
  • RSS
© 2026 Kreath Archive. All rights reserved.Built with Next.js + MDX
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 8장: infer 키워드와 타입 추론 마스터
2026년 2월 5일·프로그래밍·

8장: infer 키워드와 타입 추론 마스터

TypeScript infer 키워드의 고급 활용 패턴, infer extends 구문, 공변/반변 위치에서의 추론, 그리고 실전 타입 추출 패턴을 심층 분석합니다.

14분851자7개 섹션
typescriptperformancedevtools
공유
typescript-deepdive8 / 12
123456789101112
이전7장: 템플릿 리터럴 타입으로 문자열 다루기다음9장: NoInfer와 새로운 유틸리티 타입

7장에서 템플릿 리터럴 타입의 패턴 매칭을 다뤘습니다. 이 장에서는 그 핵심 도구인 infer 키워드를 심층적으로 분석합니다. infer는 조건부 타입 내에서 타입을 추출하는 메커니즘으로, TypeScript 타입 시스템에서 가장 강력한 도구 중 하나입니다. 특히 TypeScript 4.7에서 도입된 infer extends 구문과 5.x에서의 추론 개선 사항을 집중적으로 다룹니다.

infer의 본질

infer는 조건부 타입의 extends 절에서 타입 변수를 선언하는 키워드입니다. 패턴 매칭에서 캡처 그룹과 유사하게, 타입 구조의 특정 부분을 "캡처"합니다.

infer의 기본 메커니즘
typescript
// 패턴: (...args) => R 에서 R을 캡처
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
 
// 패턴: Promise<T>에서 T를 캡처
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
 
// 패턴: [first, ...rest]에서 first와 rest를 캡처
type First<T> = T extends [infer F, ...any[]] ? F : never;
type Rest<T> = T extends [any, ...infer R] ? R : never;
 
type F = First<[1, 2, 3]>;  // 1
type R = Rest<[1, 2, 3]>;   // [2, 3]

같은 위치의 여러 infer

같은 타입 변수를 여러 위치에서 추론하면, TypeScript는 모든 후보를 수집하여 결과를 결정합니다.

여러 위치의 infer
typescript
// 공변(covariant) 위치: 유니온으로 결합
type Foo<T> = T extends { a: infer U; b: infer U } ? U : never;
type F1 = Foo<{ a: string; b: number }>;  // string | number
 
// 반변(contravariant) 위치: 인터섹션으로 결합
type Bar<T> = T extends {
  a: (x: infer U) => void;
  b: (x: infer U) => void;
} ? U : never;
type B1 = Bar<{ a: (x: string) => void; b: (x: number) => void }>;
// string & number → never
Info

공변(Covariant) 위치란 타입이 "출력"되는 위치(반환 타입, 프로퍼티 값 등)이고, 반변(Contravariant) 위치란 타입이 "입력"되는 위치(매개변수 등)입니다. infer가 공변 위치에 있으면 유니온, 반변 위치에 있으면 인터섹션으로 결합됩니다.

이 특성을 활용하면 유니온을 인터섹션으로 변환하는 유명한 타입을 구현할 수 있습니다.

유니온 → 인터섹션 변환
typescript
type UnionToIntersection<U> =
  (U extends any ? (x: U) => void : never) extends (x: infer I) => void
    ? I
    : never;
 
type Result = UnionToIntersection<{ a: 1 } | { b: 2 } | { c: 3 }>;
// { a: 1 } & { b: 2 } & { c: 3 }

infer extends (TypeScript 4.7+)

TypeScript 4.7에서 도입된 infer extends 구문은 추론되는 타입에 제약 조건을 추가합니다. 이를 통해 추가적인 조건부 타입 없이 한 번에 타입을 필터링하고 추출할 �� 있습니다.

infer extends 기본
typescript
// 4.7 이전: 두 단계 필요
type FirstStringOld<T> =
  T extends [infer F, ...any[]]
    ? F extends string ? F : never
    : never;
 
// 4.7 이후: 한 단계로 가능
type FirstString<T> =
  T extends [infer F extends string, ...any[]] ? F : never;
 
type FS1 = FirstString<["hello", 1, 2]>;  // "hello"
type FS2 = FirstString<[42, "hi"]>;        // never

숫자 문자열 파싱

infer extends number를 사용하면 문자열에서 숫자를 추출할 수 있습니다.

문자열에서 숫자 추출
typescript
type ParseInt<S extends string> =
  S extends `${infer N extends number}` ? N : never;
 
type N1 = ParseInt<"42">;    // 42
type N2 = ParseInt<"100">;   // 100
type N3 = ParseInt<"hello">; // never
 
// 버전 문자열 파싱
type ParseVersion<V extends string> =
  V extends `${infer Major extends number}.${infer Minor extends number}.${infer Patch extends number}`
    ? { major: Major; minor: Minor; patch: Patch }
    : never;
 
type Version = ParseVersion<"5.4.2">;
// { major: 5; minor: 4; patch: 2 }

불리언 문자열 파싱

문자열에서 불리언 추출
typescript
type ParseBoolean<S extends string> =
  S extends `${infer B extends boolean}` ? B : never;
 
type B1 = ParseBoolean<"true">;   // true
type B2 = ParseBoolean<"false">;  // false
type B3 = ParseBoolean<"yes">;    // never

고급 infer 패턴

튜플 조작

튜플 조작 유틸리티
typescript
// 마지막 요소 추출
type Last<T extends any[]> =
  T extends [...any[], infer L] ? L : never;
 
type L1 = Last<[1, 2, 3]>;    // 3
type L2 = Last<["a", "b"]>;   // "b"
 
// 마지막 요소 제거
type DropLast<T extends any[]> =
  T extends [...infer Rest, any] ? Rest : never;
 
type DL = DropLast<[1, 2, 3]>;  // [1, 2]
 
// 튜플 뒤집기
type Reverse<T extends any[]> =
  T extends [infer First, ...infer Rest]
    ? [...Reverse<Rest>, First]
    : [];
 
type Rev = Reverse<[1, 2, 3, 4]>;  // [4, 3, 2, 1]
 
// 튜플 평탄화
type FlattenTuple<T extends any[]> =
  T extends [infer First, ...infer Rest]
    ? First extends any[]
      ? [...FlattenTuple<First>, ...FlattenTuple<Rest>]
      : [First, ...FlattenTuple<Rest>]
    : [];
 
type Flat = FlattenTuple<[1, [2, 3], [4, [5, 6]]]>;
// [1, 2, 3, 4, 5, 6]

함수 시그니처 분석

함수 시그니처 분해
typescript
// 첫 번째 매개변수 추출
type FirstParameter<T> =
  T extends (first: infer P, ...rest: any[]) => any ? P : never;
 
// 마지막 매개변수 추출
type LastParameter<T> =
  T extends (...args: [...any[], infer L]) => any ? L : never;
 
// 함수의 this 타입 추출
type ThisType<T> =
  T extends (this: infer U, ...args: any[]) => any ? U : never;
 
// 오버로드된 함수의 모든 반환 타입 추출
type OverloadReturnTypes<T> =
  T extends {
    (...args: any[]): infer R1;
    (...args: any[]): infer R2;
  } ? R1 | R2 : never;

생성자 타입 분석

생성자 타입 추출
typescript
// 클래스의 인스턴스 타입 추출
type InstanceType<T> =
  T extends new (...args: any[]) => infer R ? R : never;
 
// 생성자 매개변수 추출
type ConstructorParameters<T> =
  T extends new (...args: infer P) => any ? P : never;
 
class UserModel {
  constructor(
    public name: string,
    public email: string,
    public age: number
  ) {}
}
 
type UserInstance = InstanceType<typeof UserModel>;  // UserModel
type UserCtorParams = ConstructorParameters<typeof UserModel>;
// [string, string, number]

실전 패턴

타입 안전 Pipe 함수

Pipe 함수 타입
typescript
type PipeReturn<Fns extends ((...args: any[]) => any)[], Input> =
  Fns extends [infer First extends (...args: any[]) => any, ...infer Rest extends ((...args: any[]) => any)[]]
    ? PipeReturn<Rest, ReturnType<First>>
    : Input;
 
function pipe<A, B>(fn1: (a: A) => B): (a: A) => B;
function pipe<A, B, C>(fn1: (a: A) => B, fn2: (b: B) => C): (a: A) => C;
function pipe<A, B, C, D>(
  fn1: (a: A) => B,
  fn2: (b: B) => C,
  fn3: (c: C) => D
): (a: A) => D;
function pipe(...fns: ((...args: any[]) => any)[]) {
  return (input: any) => fns.reduce((acc, fn) => fn(acc), input);
}
 
const transform = pipe(
  (x: string) => x.length,      // string → number
  (n: number) => n > 5,          // number → boolean
  (b: boolean) => b ? "yes" : "no"  // boolean → string
);
 
const result = transform("TypeScript");  // "yes" (타입: string)

JSON 파싱 타입

JSON 문자열에서 타입 추출
typescript
type ParseJsonPrimitive<T extends string> =
  T extends `${infer N extends number}` ? N :
  T extends `${infer B extends boolean}` ? B :
  T extends "null" ? null :
  T extends `"${infer S}"` ? S :
  never;
 
type JP1 = ParseJsonPrimitive<"42">;      // 42
type JP2 = ParseJsonPrimitive<"true">;    // true
type JP3 = ParseJsonPrimitive<"null">;    // null
type JP4 = ParseJsonPrimitive<'"hello"'>; // "hello"

Promisify 타입

콜백 기반 함수를 Promise 기반으로 변환하는 타입입니다.

Promisify 타입 변환
typescript
type CallbackFn = (...args: [...any[], (error: any, result: any) => void]) => void;
 
type Promisify<T extends CallbackFn> =
  T extends (...args: [...infer Args, (error: any, result: infer R) => void]) => void
    ? (...args: Args) => Promise<R>
    : never;
 
type ReadFileCb = (
  path: string,
  encoding: string,
  callback: (error: Error | null, data: string) => void
) => void;
 
type ReadFileAsync = Promisify<ReadFileCb>;
// (path: string, encoding: string) => Promise<string>

재귀적 Promise Unwrap

깊은 Promise 풀기
typescript
type DeepAwaited<T> =
  T extends Promise<infer U>
    ? DeepAwaited<U>
    : T extends (...args: infer A) => infer R
      ? (...args: A) => DeepAwaited<R>
      : T extends object
        ? { [K in keyof T]: DeepAwaited<T[K]> }
        : T;
 
type Nested = Promise<Promise<Promise<string>>>;
type Resolved = DeepAwaited<Nested>;  // string
 
type FnWithPromise = () => Promise<{ data: Promise<number[]> }>;
type ResolvedFn = DeepAwaited<FnWithPromise>;
// () => { data: number[] }

TypeScript 5.x에서의 추론 개선

클로저 내 좁혀진 타입 보존 (5.4)

TypeScript 5.4에서 클로저 내 타입 좁히기(narrowing)가 보존되도록 개선되었습니다. 이는 infer 자체와 직접 관련되지는 않지만, 타입 추론의 정확성을 크게 향상시킵니다.

5.4 클로저 타입 보존
typescript
function process(value: string | number) {
  if (typeof value === "string") {
    // 5.4 이전: 클로저 안에서 value가 string | number로 확장될 수 있었음
    // 5.4 이후: string으로 보존
    const fn = () => value.toUpperCase(); // OK in 5.4+
  }
}

추론된 타입 가드 (5.5)

TypeScript 5.5에서는 함수가 타입 가드 역할을 하는지 자동으로 추론합니다.

5.5 추론된 타입 가드
typescript
// 5.5 이전: 수동으로 타입 가드를 명시해야 했음
function isNonNull<T>(value: T): value is NonNullable<T> {
  return value !== null && value !== undefined;
}
 
// 5.5 ���후: 자동 추론
function isNonNull2<T>(value: T) {
  return value !== null && value !== undefined;
}
// 반환 타입이 자동으로 value is NonNullable<T>로 추론됨
 
const values: (string | null)[] = ["hello", null, "world", null];
const filtered = values.filter(isNonNull2);
// 5.5+: string[] (이전에는 (string | null)[]이었음)

infer 사용 시 주의사항

typescript
// 1. infer는 반드시 조건부 타입의 extends 절에서만 사용 가능
type Bad = infer T; // Error
 
// 2. infer 변수는 true 분기에서만 사용 가능
type AlsoBad<T> = T extends (infer U)[] ? string : U; // Error: U를 false 분기에서 사용
 
// 3. 과도한 재귀는 성능 문제를 유발
// TypeScript는 재귀 깊이를 제한하며, 초과 시 에러 발생
Tip

infer를 활용한 복잡한 타입은 가독성이 떨어질 ��� 있습니다. 중간 단계의 타입에 의미 있는 이름을 부여하고, JSDoc 주석으로 의도를 문서화하면 유지보수성이 크�� 향상됩니다.

정리

infer 키워드는 TypeScript 타입 시스템에서 패턴 매칭과 타입 추출의 핵심입니다. 함수 시그니처, 튜플, 문자열, Promise 등 다양한 타입 구조에서 원하는 부분을 정밀하게 추출할 수 있습니다. infer extends(4.7+)는 추출과 필터링을 한 번에 수행하게 해주며, TypeScript 5.x의 추론 개선(클로저 보존, 추론된 타입 가드)은 infer와 함께 전체적인 타입 추론 경험을 향상시킵니다.

다음 장에서는 TypeScript 5.4에서 도입된 NoInfer와 새로운 유틸리티 타입을 다룹니다. 라이브러리 설계에서 타입 추론의 방향을 정밀하게 제어하는 방법을 배웁니다.

이 글이 도움이 되셨나요?

관련 주제 더 보기

#typescript#performance#devtools

관련 글

프로그래밍

9장: NoInfer와 새로운 유틸리티 타입

TypeScript 5.4의 NoInfer 유틸리티 타입과 5.x에서 추가된 새로운 타입 도구들을 활용한 라이브러리 설계 패턴을 다룹니다.

2026년 2월 7일·14분
프로그래밍

7장: 템플릿 리터럴 타입으로 문자열 다루기

TypeScript 템플릿 리터럴 타입의 원리, 내장 문자열 조작 유틸리티, 패턴 매칭, 그리고 매핑 타입과의 결합을 통한 실전 패턴을 다룹니다.

2026년 2월 3일·12분
프로그래밍

10장: 타입 수준 프로그래밍 — 타입으로 로직 작성하기

TypeScript 타입 시스템을 프로그래밍 언어로 활용하는 고급 기법 — 산술 연산, 문자열 파서, 상태 머신 등을 타입만으로 구현하는 패턴을 다룹니다.

2026년 2월 9일·14분
이전 글7장: 템플릿 리터럴 타입으로 문자열 다루기
다음 글9장: NoInfer와 새로운 유틸리티 타입

댓글

목차

약 14분 남음
  • infer의 본질
    • 같은 위치의 여러 infer
  • infer extends (TypeScript 4.7+)
    • 숫자 문자열 파싱
    • 불리언 문자열 파싱
  • 고급 infer 패턴
    • 튜플 조작
    • 함수 시그니처 분석
    • 생성자 타입 분석
  • 실전 패턴
    • 타입 안전 Pipe 함수
    • JSON 파싱 타입
    • Promisify 타입
    • 재귀적 Promise Unwrap
  • TypeScript 5.x에서의 추론 개선
    • 클로저 내 좁혀진 타입 보존 (5.4)
    • 추론된 타입 가드 (5.5)
  • infer 사용 시 주의사항
  • 정리