← Назад к вопросам
Что такое infer в TypeScript?
2.8 Senior🔥 181 комментариев
#TypeScript#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
infer в TypeScript
infer - это ключевое слово в TypeScript, которое позволяет автоматически извлекать (вывести) типы из сложных типовых выражений. Используется только внутри условных типов (conditional types).
Синтаксис
type SomeType<T> = T extends SomePattern<infer U> ? U : never;
инфер работает как "переменная для типов" - TypeScript пытается понять, какой тип скрывается в определенной позиции.
Простой пример
// Извлечение типа из массива
type GetArrayType<T> = T extends (infer U)[] ? U : never;
type NumArray = number[];
type StringArray = string[];
type NumType = GetArrayType<NumArray>; // number
type StrType = GetArrayType<StringArray>; // string
type NotArray = GetArrayType<number>; // never (число не массив)
Что здесь происходит:
GetArrayType<number[]>- проверяем, является лиnumber[]массивом- Если да,
infer U"ловит" тип элемента (number) - Возвращаем
U(которыйnumber)
Извлечение типа из Promise
Одна из самых полезных применений:
// Получаем тип, который Promise возвращает
type Awaited<T> = T extends Promise<infer U> ? U : T;
type Response = Promise<string>;
type Result = Awaited<Response>; // string
type DirectValue = Awaited<number>; // number (не Promise)
// Практически:
const promise: Promise<{ id: number; name: string }> = fetch(/api/user).then(r => r.json());
type User = Awaited<typeof promise>; // { id: number; name: string }
Извлечение типов из функций
// Получаем тип параметра функции
type GetFirstParam<T> = T extends (param: infer P, ...args: any[]) => any ? P : never;
function greet(name: string, age: number) {
return `Hello, ${name}`;
}
type NameParam = GetFirstParam<typeof greet>; // string
// Получаем тип возвращаемого значения функции
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type GreetReturn = GetReturnType<typeof greet>; // string
Практический пример: React hooks
существует utility type ReturnType в TypeScript, построенный на infer:
// Внутренняя реализация ReturnType использует infer
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
function getCurrentUser(): { id: string; name: string; email: string } {
return { id: "123", name: "John", email: "john@example.com" };
}
type User = ReturnType<typeof getCurrentUser>;
// User = { id: string; name: string; email: string }
// В компоненте
const user: User = getCurrentUser();
user.id; // OK
user.phone; // ERROR: Property "phone" does not exist
Извлечение типов из union типов
// Получаем последний тип из union
type GetLast<T> = T extends [infer _,...infer Last] ? Last extends [infer U] ? U : Last[number] : never;
type Colors = GetLast<["red", "green", "blue"]>; // "blue"
// Получаем все типы кроме первого
type Tail<T> = T extends [infer _,...infer Rest] ? Rest : [];
type Rest = Tail<[string, number, boolean]>; // [number, boolean]
Сложный пример: распаковка вложенных типов
// Рекурсивно распаковываем Promise<Promise<T>>
type Deep<T> = T extends Promise<infer U>
? Deep<U> // Если Promise, применяем правило к U
: T; // Иначе возвращаем как есть
type A = Deep<Promise<Promise<Promise<string>>>>; // string
type B = Deep<number>; // number
type C = Deep<Promise<string>>; // string
Использование с extends и условными типами
// Проверяем формат и извлекаем данные
type ExtractEmail<T> = T extends `${infer Name}@${infer Domain}` ? { name: Name; domain: Domain } : null;
type Result1 = ExtractEmail<"john@example.com">;
// { name: "john"; domain: "example.com" }
type Result2 = ExtractEmail<"not-an-email">;
// null
Множественные infer в одном типе
// Извлекаем части URL
type ParseURL<T> = T extends `${infer Protocol}://${infer Domain}${infer Path}`
? { protocol: Protocol; domain: Domain; path: Path }
: never;
type URL = ParseURL<"https://example.com/api/users">;
// { protocol: "https"; domain: "example.com"; path: "/api/users" }
Практический пример: создание типов из API ответов
// Вместо ручного написания типов, извлекаем их из функции
type AsyncReturnType<T extends (...args: any) => Promise<any>> =
T extends (...args: any) => Promise<infer R> ? R : never;
async function fetchUser(id: string) {
const response = await fetch(`/api/users/${id}`);
return response.json() as { id: number; name: string; email: string };
}
type User = AsyncReturnType<typeof fetchUser>;
// { id: number; name: string; email: string }
// Теперь если API изменится, тип обновится автоматически!
infer vs явная типизация
// Вариант 1: явная типизация (скучно)
function getData(promise: Promise<string>): string {
return "result";
}
// Вариант 2: с использованием infer (гибко)
function getData<T>(promise: Promise<T>): T {
return null as any;
}
// Вариант 3: utility type с infer (переиспользуемо)
type Unwrap<T extends Promise<any>> = T extends Promise<infer U> ? U : never;
type Result = Unwrap<Promise<{ status: "ok" }>); // { status: "ok" }
Зачем это нужно на практике
- DRY принцип: не дублируешь типы, вычисляешь их
- Автоматическая синхронизация: если API изменился, типы обновятся
- Безопасность: TypeScript проверит соответствие типов
- Переиспользование: one type to rule them all
Итоги
- infer извлекает типы из сложных выражений
- Используется только в условных типах (
extends) - Позволяет создавать generic и переиспользуемые типы
- Часто используется с Promise, Function, Array
- Делает TypeScript код более DRY и maintainable
- Мощный инструмент для advanced типизации