Infer: извлечение типов

infer — ключевое слово TypeScript для использования внутри conditional types: объявляет типовую переменную, которую компилятор должен вывести (infer) из сопоставляемого типа.

Зачем нужно

infer позволяет «вытащить» часть типа из сложной структуры без явного указания — TypeScript сам вычисляет нужный фрагмент. Это основа для написания продвинутых утилитарных типов вроде ReturnType, Parameters, Awaited, UnpackPromise.

Где используется

  • Реализация стандартных утилит: ReturnType, Parameters, InstanceType, Awaited
  • Извлечение типа элемента массива, аргументов функции, разрешённого типа промиса
  • Парсинг строковых шаблонных литералов на уровне типов
  • Тип-уровневые трансформации в сложных библиотеках

Основной контент

Синтаксис infer

infer используется только в ветке extends conditional type. Переменная, объявленная через infer, доступна только в ветке true.

// Общая форма:
type MyType<T> = T extends SomePattern<infer U> ? U : FallbackType;

Извлечение типа возвращаемого значения

// Аналог ReturnType из стандартной библиотеки
type MyReturnType<T extends (...args: any) => any> =
  T extends (...args: any) => infer R ? R : never;

function greet(name: string): string {
  return `Hello, ${name}`;
}

type Result = MyReturnType<typeof greet>; // string

Извлечение типов параметров

type MyParameters<T extends (...args: any) => any> =
  T extends (...args: infer P) => any ? P : never;

function add(a: number, b: number): number {
  return a + b;
}

type Params = MyParameters<typeof add>; // [a: number, b: number]

Извлечение типа элемента массива

type ArrayElement<T> = T extends (infer U) ? U : never;

type E1 = ArrayElement<string>;           // string
type E2 = ArrayElement<(number | boolean)>; // number | boolean
type E3 = ArrayElement<string>;             // never — не массив

Извлечение типа промиса

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

type P1 = UnwrapPromise<Promise<string>>; // string
type P2 = UnwrapPromise<number>;          // number (не промис)

Несколько infer в одном conditional type

// Первый и последний элементы кортежа
type Head<T extends any> = T extends [infer H, ...any] ? H : never;
type Tail<T extends any> = T extends [...any, infer L] ? L : never;

type H = Head<[1, 2, 3]>; // 1
type L = Tail<[1, 2, 3]>; // 3

// Разделение аргументов функции
type FirstArg<T> = T extends (first: infer F, ...rest: any) => any ? F : never;

function process(id: number, name: string): void {}
type FArg = FirstArg<typeof process>; // number

Строковые шаблоны с infer (TS 4.1+)

type GetEventName<T extends string> =
  T extends `on${infer Event}` ? Event : never;

type Click  = GetEventName<"onClick">;  // "Click"
type Change = GetEventName<"onChange">; // "Change"
type None   = GetEventName<"value">;    // never

Частые ошибки

  • Использовать infer вне conditional typeinfer работает только внутри T extends ... ? ... : ....
  • Использовать infer в false-ветке — переменная infer U доступна только в true-ветке (? U), не в false.
  • Забыть ограничить T перед infer — без T extends (...args: any) => any вывод параметров функции не сработает.
  • Смешивать infer с обычными generic-параметрамиinfer объявляет новую переменную, не переиспользует внешние.

Связанные темы

Ресурсы