14. Ключевое слово infer
TypeScript: Броня. Урок 13: Ключевое слово infer
Заголовок раздела «TypeScript: Броня. Урок 13: Ключевое слово infer»Ключевое слово infer - это мощный инструмент TypeScript, который позволяет извлекать и сохранять типы внутри условных типов. Оно работает как “захват” типа: TypeScript пытается вывести тип из структуры и сохранить его в переменную типа. Это открывает огромные возможности для создания гибких и умных типов.
Базовый синтаксис
Заголовок раздела «Базовый синтаксис»infer используется только внутри условной части extends в условных типах:
type ExtractType<T> = T extends SomePattern<infer U> ? U : never;Где:
infer U- объявление переменной типаU, которую TypeScript попытается вывести- Если вывод успешен, возвращается
U - Если нет - возвращается тип из
falseветки
Простые примеры
Заголовок раздела «Простые примеры»// Пример 1: Извлечение типа из массиваtype ArrayElement<T> = T extends (infer U)[] ? U : never;
type Strings = ArrayElement<string[]>; // stringtype Numbers = ArrayElement<number[]>; // numbertype NotArray = ArrayElement<boolean>; // never
// Пример 2: Извлечение возвращаемого типа функцииtype ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type Func1 = () => string;type Func2 = (x: number) => boolean;
type Return1 = ReturnType<Func1>; // stringtype Return2 = ReturnType<Func2>; // boolean
// Пример 3: Извлечение типов параметровtype Parameters<T> = T extends (...args: infer P) => any ? P : never;
type Params1 = Parameters<(a: string, b: number) => void>;// [a: string, b: number]
type Params2 = Parameters<(x: boolean) => void>;// [x: boolean]Извлечение из Promise
Заголовок раздела «Извлечение из Promise»// Распаковка Promisetype UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type AsyncString = UnwrapPromise<Promise<string>>; // stringtype SyncNumber = UnwrapPromise<number>; // number
// Рекурсивная распаковка вложенных Promisetype DeepUnwrapPromise<T> = T extends Promise<infer U> ? DeepUnwrapPromise<U> : T;
type Nested = DeepUnwrapPromise<Promise<Promise<Promise<number>>>>;// number
// Практический пример с async функциямиtype AsyncReturnType<T> = T extends (...args: any[]) => Promise<infer R> ? R : T extends (...args: any[]) => infer R ? R : never;
async function fetchUser() { return { id: 1, name: "Alice" };}
type User = AsyncReturnType<typeof fetchUser>;// { id: number; name: string }Множественные infer
Заголовок раздела «Множественные infer»Можно использовать несколько infer в одном условном типе:
// Извлечение первого и последнего элемента кортежаtype FirstAndLast<T> = T extends [infer First, ...any[], infer Last] ? [First, Last] : never;
type Result1 = FirstAndLast<[1, 2, 3, 4, 5]>;// [1, 5]
type Result2 = FirstAndLast<["a", "b", "c"]>;// ["a", "c"]
// Извлечение типов из функцииtype FunctionParts<T> = T extends ( ...args: infer Args) => infer Return ? { args: Args; return: Return } : never;
type Parts = FunctionParts<(x: number, y: string) => boolean>;// { args: [x: number, y: string]; return: boolean }
// Разбор generic типовtype UnwrapArray<T> = T extends Array<infer U> ? U : T;type UnwrapSet<T> = T extends Set<infer U> ? U : T;type UnwrapMap<T> = T extends Map<infer K, infer V> ? [K, V] : T;
type ArrType = UnwrapArray<string[]>; // stringtype SetType = UnwrapSet<Set<number>>; // numbertype MapType = UnwrapMap<Map<string, User>>; // [string, User]Практические примеры
Заголовок раздела «Практические примеры»// Извлечение типа из конструктораtype InstanceType<T> = T extends new (...args: any[]) => infer R ? R : never;
class User { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; }}
type UserInstance = InstanceType<typeof User>;// User
// Извлечение типа элемента из Observable/EventEmittertype ExtractEventType<T> = T extends EventEmitter<infer E> ? E : never;
class EventEmitter<T> { emit(event: T) { /* ... */ }}
type StringEmitter = EventEmitter<string>;type EventType = ExtractEventType<StringEmitter>; // string
// Flatten массиваtype Flatten<T> = T extends Array<infer U> ? Flatten<U> : T;
type Deep = Flatten<string[][][]>; // stringtype Shallow = Flatten<number[]>; // numberЖизненный пример: Type-safe Redux Actions
Заголовок раздела «Жизненный пример: Type-safe Redux Actions»// Система типизированных actions для Reduxtype ActionCreator<T = any> = (...args: any[]) => { type: string; payload: T };
type ExtractPayload<T> = T extends ActionCreator<infer P> ? P : never;
type ExtractAction<T> = T extends (...args: any[]) => infer A ? A : never;
// Определение action creatorsconst loginAction = (userId: string, token: string) => ({ type: 'USER_LOGIN' as const, payload: { userId, token },});
const logoutAction = () => ({ type: 'USER_LOGOUT' as const, payload: undefined,});
const updateProfileAction = (name: string, email: string) => ({ type: 'PROFILE_UPDATE' as const, payload: { name, email },});
// Извлечение типовtype LoginPayload = ExtractPayload<typeof loginAction>;// { userId: string; token: string }
type LoginAction = ExtractAction<typeof loginAction>;// { type: 'USER_LOGIN'; payload: { userId: string; token: string } }
// Создание union всех actionstype Actions = | ExtractAction<typeof loginAction> | ExtractAction<typeof logoutAction> | ExtractAction<typeof updateProfileAction>;
// Type-safe reducerfunction reducer(state: State, action: Actions) { switch (action.type) { case 'USER_LOGIN': // TypeScript знает, что action.payload имеет тип { userId: string; token: string } return { ...state, userId: action.payload.userId }; case 'USER_LOGOUT': // TypeScript знает, что action.payload имеет тип undefined return { ...state, userId: null }; case 'PROFILE_UPDATE': // TypeScript знает точный тип payload return { ...state, profile: action.payload }; }}Разбор строковых литералов
Заголовок раздела «Разбор строковых литералов»// Извлечение параметров из template literal typestype ExtractParam<T> = T extends `:${infer Param}` ? Param : never;
type UserId = ExtractParam<":id">; // "id"type PostId = ExtractParam<":postId">; // "postId"type Nothing = ExtractParam<"users">; // never
// Разбор сложных путейtype ExtractRouteParams<T extends string> = T extends `${infer _}/:${infer Param}/${infer Rest}` ? { [K in Param]: string } & ExtractRouteParams<`/${Rest}`> : T extends `${infer _}/:${infer Param}` ? { [K in Param]: string } : {};
type Params1 = ExtractRouteParams<"/users/:id">;// { id: string }
type Params2 = ExtractRouteParams<"/users/:userId/posts/:postId">;// { userId: string; postId: string }
// Разбор типов в строкахtype ParseCSSValue<T> = T extends `${infer Num}${infer Unit}` ? { value: Num; unit: Unit } : never;
type Parsed1 = ParseCSSValue<"16px">;// { value: "16"; unit: "px" }
type Parsed2 = ParseCSSValue<"2rem">;// { value: "2"; unit: "rem" }Вариадические кортежи с infer
Заголовок раздела «Вариадические кортежи с infer»// Извлечение первого элементаtype First<T> = T extends [infer F, ...any[]] ? F : never;
type FirstNum = First<[1, 2, 3]>; // 1type FirstStr = First<["a", "b"]>; // "a"
// Извлечение всех элементов кроме первогоtype Tail<T> = T extends [any, ...infer Rest] ? Rest : never;
type TailResult = Tail<[1, 2, 3, 4]>; // [2, 3, 4]
// Извлечение последнего элементаtype Last<T> = T extends [...any[], infer L] ? L : never;
type LastNum = Last<[1, 2, 3]>; // 3
// Реверс кортежа (рекурсивно)type Reverse<T> = T extends [infer First, ...infer Rest] ? [...Reverse<Rest>, First] : T;
type Reversed = Reverse<[1, 2, 3, 4]>;// [4, 3, 2, 1]Продвинутые паттерны
Заголовок раздела «Продвинутые паттерны»// Извлечение readonly модификатораtype IsReadonly<T, K extends keyof T> = { readonly [P in K]: T[P];} extends { [P in K]: T[P] } ? false : true;
// Извлечение optional модификатораtype IsOptional<T, K extends keyof T> = {} extends Pick<T, K> ? true : false;
// Deep Property Access Typetype DeepValue<T, Path> = Path extends `${infer Key}.${infer Rest}` ? Key extends keyof T ? DeepValue<T[Key], Rest> : never : Path extends keyof T ? T[Path] : never;
interface Config { server: { ssl: { enabled: boolean; cert: string; }; };}
type SSLEnabled = DeepValue<Config, "server.ssl.enabled">; // booleantype CertPath = DeepValue<Config, "server.ssl.cert">; // stringКлючевые моменты
Заголовок раздела «Ключевые моменты»inferпозволяет извлекать типы из структур внутри условных типов- Работает только в части
extendsусловного типа - Можно использовать несколько
inferв одном условии - Основа для многих встроенных utility типов (
ReturnType,Parameters,InstanceType) - Мощно комбинируется с template literal types для parsing строк
- Используется для рекурсивных типов и работы с вариадическими кортежами
- Критически важен для создания type inference в библиотеках и фреймворках
- Позволяет TypeScript “понимать” сложные паттерны кода и выводить типы автоматически
- Делает возможным создание мета-программирования на уровне типов