Что такое infer в TypeScript?
infer — ключевое слово в условных типах TypeScript, которое позволяет «вытащить» (вывести) часть типа в новую переменную типа прямо внутри выражения extends.Что такое infer
infer используется исключительно внутри условных типов (conditional types) в позиции extends. Оно объявляет временную переменную типа, которую TypeScript заполняет в момент проверки условия. Если условие выполняется — переменная доступна в ветке true.
Синтаксис:
T extends SomeType<infer U> ? U : never
Здесь U — выведенный тип. Если T соответствует SomeType<...>, то U будет равен аргументу этого дженерика.
Зачем это нужно
Без infer невозможно написать утилитарные типы, которые «разбирают» чужой тип на части. Именно на infer построены встроенные утилиты TypeScript: ReturnType, Parameters, InstanceType, Awaited.
Практические примеры
Извлечение возвращаемого типа функции
Встроенный ReturnType<T> реализован так:
type ReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : never;
TypeScript смотрит: «Является ли T функцией? Если да — выведи тип возврата в R».
Извлечение типа элемента массива
type ElementOf<T> = T extends Array<infer Item> ? Item : never;
type A = ElementOf<string[]>; // string
type B = ElementOf<number[]>; // number
Раскрытие вложенного Promise
Механика Awaited выглядит примерно так:
type Awaited<T> =
T extends Promise<infer V> ? Awaited<V> : T;
type R = Awaited<Promise<Promise<number>>>; // number
Извлечение типов параметров функции
type FirstParam<T extends (...args: any[]) => any> =
T extends (first: infer F, ...rest: any[]) => any ? F : never;
function greet(name: string, age: number) {}
type N = FirstParam<typeof greet>; // string
Ограничения и нюансы
inferработает только внутриextendsв условном типе — нигде больше.- Одна и та же переменная
inferможет появляться в нескольких местах; тогда TypeScript пытается вывести пересечение или объединение типов в зависимости от позиции (ковариантная / контравариантная). - В контравариантных позициях (параметры функций) несколько
infer Uобразуют пересечение (&), в ковариантных — объединение (|). inferнельзя использовать вне дистрибутивных условных типов без обёртки в кортеж:[T] extends [infer U]— способ отключить дистрибутивность.
Дистрибутивность и infer
Когда T — голый тип-параметр, условный тип раскрывается по каждому члену объединения:
type Flatten<T> = T extends Array<infer U> ? U : T;
type R = Flatten<string[] | number[]>; // string | number
Что хочет услышать интервьюер
Кандидат объясняет, что `infer` работает только внутри условных типов в позиции `extends` и позволяет объявить переменную типа «на лету»
Знание встроенных утилит, построенных на `infer`: `ReturnType`, `Parameters`, `Awaited`, `InstanceType`
Понимание дистрибутивности условных типов и того, как она взаимодействует с `infer`
Умение написать собственный утилитарный тип с `infer` — например, извлечь тип элемента массива или первый аргумент функции
Знание о поведении `infer` в ковариантных и контравариантных позициях (объединение vs пересечение)
Пример: Базовый пример: извлечение типа возврата
// Собственная реализация ReturnType
type MyReturnType<T extends (...args: any[]) => any> =
T extends (...args: any[]) => infer R ? R : never;
function fetchUser(): Promise<{ id: number; name: string }> {
return Promise.resolve({ id: 1, name: 'Alice' });
}
type UserResponse = MyReturnType<typeof fetchUser>;
// Promise<{ id: number; name: string }>
Пример: Извлечение типа элемента массива или кортежа
type ElementOf<T> = T extends ReadonlyArray<infer Item> ? Item : never;
type Str = ElementOf<string[]>; // string
type Num = ElementOf<readonly number[]>; // number
type Mixed = ElementOf<[string, number]>; // string | number
Пример: Рекурсивное раскрытие вложенных Promise (аналог Awaited)
type DeepAwaited<T> =
T extends Promise<infer Inner>
? DeepAwaited<Inner>
: T;
type A = DeepAwaited<Promise<Promise<Promise<string>>>>; // string
type B = DeepAwaited<number>; // number
Пример: Контравариантная позиция: пересечение типов
// В позиции параметра функции infer формирует пересечение
type UnionToIntersection<U> =
(U extends any ? (x: U) => void : never) extends (x: infer I) => void
? I
: never;
type Result = UnionToIntersection<{ a: string } | { b: number }>;
// { a: string } & { b: number }
Типичные ошибки
Попытка использовать `infer` вне условного типа (например, в обычном дженерике) — это синтаксическая ошибка
Путаница между `infer` и обычным параметром дженерика: кандидат не понимает, зачем нужен `infer`, если можно просто передать тип аргументом
Незнание, что `infer` в нескольких позициях одного условного типа может давать пересечение (контравариантная позиция) вместо ожидаемого объединения
Забывают про дистрибутивность: не знают, как с помощью кортежа `[T] extends [infer U]` её отключить
Не могут назвать ни одного встроенного типа, реализованного через `infer`, хотя пользуются `ReturnType` каждый день


