← Назад к вопросам

Что такое 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 (число не массив)

Что здесь происходит:

  1. GetArrayType<number[]> - проверяем, является ли number[] массивом
  2. Если да, infer U "ловит" тип элемента (number)
  3. Возвращаем 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" }

Зачем это нужно на практике

  1. DRY принцип: не дублируешь типы, вычисляешь их
  2. Автоматическая синхронизация: если API изменился, типы обновятся
  3. Безопасность: TypeScript проверит соответствие типов
  4. Переиспользование: one type to rule them all

Итоги

  • infer извлекает типы из сложных выражений
  • Используется только в условных типах (extends)
  • Позволяет создавать generic и переиспользуемые типы
  • Часто используется с Promise, Function, Array
  • Делает TypeScript код более DRY и maintainable
  • Мощный инструмент для advanced типизации