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

Для чего нужен Infer?

2.2 Middle🔥 181 комментариев
#Soft Skills и рабочие процессы

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Для чего нужен Infer в TypeScript

Infer — это ключевое слово в TypeScript для извлечения и создания новых типов из уже существующих типов. Это мощный инструмент для работы с условными типами, позволяющий делать тип-безопасное кодирование.

Основная идея

Infer позволяет сказать TypeScript: "Посмотри на этот тип и вытащи из него конкретную часть". Это напоминает деструктуризацию, но для типов.

// Без infer — нужно писать вручную
type MyArray = string[];
type Element = string; // Пришлось написать вручную

// С infer — TypeScript вытащит автоматически
type MyArray2 = string[];
type Element2 = MyArray2 extends (infer T)[] ? T : never;
// Element2 = string (TypeScript вывел автоматически)

Основной синтаксис

// Условный тип с infer
type MyType<T> = T extends Something<infer U> ? U : never;

// Читается как:
// "Если T соответствует Pattern с типом U,
// то верни U, иначе верни never"

Практические примеры

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

// Возвращает тип элемента из массива
type ArrayElement<T> = T extends (infer U)[] ? U : never;

type StringArray = string[];
type Element = ArrayElement<StringArray>; // string

type NumberArray = number[];
type Element2 = ArrayElement<NumberArray>; // number

// Это то же самое что Array.prototype's built-in
type MyArray = [1, 2, 3];
type MyElement = MyArray[number]; // 1 | 2 | 3

2. Извлечение типа из Promise

// Вытаскиваем тип, который вернет Promise
type Awaited<T> = T extends Promise<infer U> ? U : T;

type PromiseOfString = Promise<string>;
type StringType = Awaited<PromiseOfString>; // string

type JustString = string;
type StringType2 = Awaited<JustString>; // string (если не Promise)

// Полезно при работе с async функциями
async function fetchData() {
  return { id: 1, name: 'John' };
}

type FetchResult = Awaited<ReturnType<typeof fetchData>>;
// { id: number; name: string }

3. Извлечение типа параметра функции

// Берем тип первого параметра функции
type FirstParam<T> = T extends (first: infer U, ...rest: any[]) => any
  ? U
  : never;

const handleClick = (event: MouseEvent) => {
  console.log(event);
};

type ClickParam = FirstParam<typeof handleClick>; // MouseEvent

// Работает и с конструкторами
class User {
  constructor(id: number, name: string) {}
}

type UserParams = FirstParam<typeof User>; // number

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

type ReturnType<T> = T extends (...args: any[]) => infer R
  ? R
  : never;

const getData = (): { id: number; title: string } => ({
  id: 1,
  title: 'Test'
});

type DataType = ReturnType<typeof getData>;
// { id: number; title: string }

// Встроенный ReturnType делает то же самое
type DataType2 = ReturnType<typeof getData>;

5. Извлечение из Union типов

// Вытаскиваем конкретный элемент из объединения (Union)
type Flatten<T> = T extends Array<infer U> ? U : T;

type MyType = Flatten<string[]>; // string
type MyType2 = Flatten<string>; // string (если не массив)

// Более сложный пример: вытащить все типы из Union
type Union = string | number | boolean;

// К сожалению, нельзя вытащить "все" из Union одной строкой
// Нужно использовать распределенные условные типы
type UnwrapArray<T> = T extends Array<infer U> ? U : T;

type Numbers = UnwrapArray<number[]>; // number
type Strings = UnwrapArray<string[]>; // string

Распределенные условные типы (Distributive Conditional Types)

Это продвинутая возможность, которая применяется к Union типам:

// Условные типы распределяются для каждого члена Union
type Flatten<T> = T extends Array<infer U> ? U : T;

type StringOrNumber = string | number;
type Result = Flatten<(string | number)[]>;
// Result = string | number (применяется к каждому члену Union)

// Пример: извлечь только строки из Union
type ExtractString<T> = T extends string ? T : never;

type Mixed = string | number | boolean;
type OnlyString = ExtractString<Mixed>; // string (остальные стали never)

// Это работает потому, что Union распределяется:
// ExtractString<string> | ExtractString<number> | ExtractString<boolean>
// = string | never | never
// = string

React примеры

Пример 1: Извлечение props типа компонента

import { FC, ComponentProps } from 'react';

interface ButtonProps {
  onClick: () => void;
  disabled?: boolean;
  children: React.ReactNode;
}

const Button: FC<ButtonProps> = ({ onClick, disabled, children }) => {
  return <button onClick={onClick}>{children}</button>;
};

// Вытащить тип props из компонента
type ButtonPropsType = ComponentProps<typeof Button>;
// = ButtonProps

Пример 2: Строго типизированный useSelector для Redux

import { useSelector } from 'react-redux';
import { AppState } from './store';

// Функция, которая возвращает строго типизированный selector
type AppDispatch = typeof store.dispatch;
type RootState = ReturnType<typeof store.getState>;

// Теперь при использовании
const user = useSelector((state: RootState) => state.user);
// TypeScript знает, что это типа User

Пример 3: Условный рендеринг с типизацией

type ExtractArrayType<T> = T extends Array<infer U> ? U : T;

interface Props<T> {
  items: T[];
  render: (item: ExtractArrayType<T[]>) => React.ReactNode;
}

const List = <T,>({ items, render }: Props<T>) => {
  return <ul>{items.map((item) => <li key={item.id}>{render(item)}</li>)}</ul>;
};

// Использование: TypeScript знает, что item имеет тип из массива
<List
  items={[{ id: 1, name: 'John' }]}
  render={(item) => item.name} // item правильно типизирован
/>

Утилиты TypeScript, использующие Infer

В стандартной библиотеке TypeScript множество утилит построены на Infer:

// ReturnType — извлекает возвращаемый тип
type MyReturn = ReturnType<() => string>; // string

// Parameters — извлекает типы параметров в кортеж
type MyParams = Parameters<(a: string, b: number) => void>;
// [a: string, b: number]

// ConstructorParameters — параметры конструктора
type UserParams = ConstructorParameters<typeof User>;
// [id: number, name: string]

// InstanceType — тип экземпляра класса
type UserInstance = InstanceType<typeof User>; // User

// Awaited — распаковка Promise (добавлено в TypeScript 4.5)
type PromiseResult = Awaited<Promise<Promise<string>>>; // string

Сложный пример: Строго типизированный fetch

// Функция, которая вытаскивает ожидаемый тип ответа
type ExtractResponse<T> = T extends { response: infer R } ? R : never;

interface ApiEndpoint {
  request: { id: number };
  response: { user: string };
}

// Теперь нашу функцию
function request<T extends ApiEndpoint>(
  endpoint: T,
  data: T['request']
): Promise<ExtractResponse<T>> {
  // ...
  return fetch('/api', { body: JSON.stringify(data) }).then(r => r.json());
}

// Использование: TypeScript знает точный тип ответа
const userApi: ApiEndpoint = {
  request: { id: 1 },
  response: { user: 'John' }
};

const result = await request(userApi, { id: 1 });
// result имеет тип { user: string }

Когда НЕ использовать Infer

// СЛИШКОМ СЛОЖНО: если код становится нечитаемым
type TooComplex<T> = T extends {
  a: infer A extends { b: infer B extends { c: infer C } }
} ? [A, B, C] : never;

// ЛУЧШЕ: разбить на части
type GetA<T> = T extends { a: infer A } ? A : never;
type GetB<T> = T extends { b: infer B } ? B : never;
type GetC<T> = T extends { c: infer C } ? C : never;

Отладка Infer типов

// Если ты не уверен, что вытащит infer, используй
type Debug<T> = T;

type MyResult = Debug<ExtractResponse<ApiEndpoint>>;
// Наведи мышку в IDE и увидишь точный тип

// Или используй console trick (работает в VS Code Intellisense)
type Unwrap<T> = T extends Promise<infer U> ? U : T;
type Test = Unwrap<Promise<string>>;

// Используй "Go to Definition" чтобы видеть результат

Вывод

Infer нужен для:

  1. Извлечения типов из сложных структур (массивы, Promise, функции)
  2. Условных типов — создания типов на основе других типов
  3. Обобщенного кода — функции, которые работают с любыми типами, но сохраняют строгость
  4. Утилит типов — создания удобных хелперов для типизации

Используй Infer когда:

  • Нужно работать с типами функций/классов, которые варьируются
  • Хочешь сделать код более универсальным без потери типобезопасности
  • Работаешь с библиотеками, которые требуют извлечения вложенных типов
  • Создаешь переиспользуемые типовые утилиты

Интересующимся: исследуй встроенные утилиты TypeScript (ReturnType, Parameters, Awaited) — они все используют Infer!

Для чего нужен Infer? | PrepBro