Ключевое слово infer в TypeScript
infer — это ключевое слово в условных типах TypeScript, используемое для вывода новых типов из существующих типов.
Оно позволяет извлекать и выводить типы внутри обобщённых условных типов, обеспечивая мощные операции над типами.
SVG-диаграмма: как работает infer
Зачем нужно ключевое слово infer?
В обобщённом программировании нам часто нужно извлекать частичные типы из сложных типов.
Например, изPromise<string>extractstring, fromArray<User>extractUser。
Ключевое слово infer делает такое извлечение типов элегантным и типобезопасным. Оно позволяет объявить переменную типа в условном типе, а затем использовать её в ветке true.
Пояснение понятия:infer — это сокращение от "infer", означающее "выводить". Его можно использовать только в части extends условного типа, чтобы объявить переменную типа, которую нужно вывести.
Базовое использование
Используйте infer для вывода типов в условных типах.
Пример
// Если T — это Promise<V>, вернуть V; иначе вернуть never
type ValueOf<T> = T extends Promise<infer V> ? V : never;
// Тест
type Str = ValueOf<Promise<string>>; // string
type Num = ValueOf<Promise<number>>; // number
type NotPrm = ValueOf<string>; // never
// Пример использования
var promise: Promise<string> = Promise.resolve("hello");
var value: ValueOf<typeof promise> = "world";
console.log("String extraction: " + (typeof value === "string" ? "Success" : "Failure"));
Вывод:
String extraction: success
Пояснение:
infer VОбъявляется переменная типа V, и TypeScript автоматически выведет тип V.
Извлечение типов элементов массива
Извлеките тип элемента из типа массива.
Пример
// Если T — массив V[], вернуть V
type ArrayElement<T> = T extends (infer V)[] ? V : never;
// Тест
type User = { name: string };
type Users = User[];
type E1 = ArrayElement<string[]>; // string
type E2 = ArrayElement<Users>; // User
type E3 = ArrayElement<number>; // never (не массив)
// Пример использования
var users: Users = [{ name: "Alice" }];
var element: ArrayElement<Users> = users[0];
console.log("Тип элемента: " + element.name);
Варианты использования:Этот шаблон часто используется для извлечения элементов определённого типа из union-типов.
Извлечение типов возвращаемых значений функций
Извлеките тип возвращаемого значения из типа функции.
Пример
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
// Тест
function getData() {
return { id: 1, name: "Alice" };
}
function fetchUser(id: number): Promise<User> {
return Promise.resolve({ id, name: "Bob" });
}
type R1 = ReturnType<typeof getData>; // { id: number; name: string }
type R2 = ReturnType<typeof fetchUser>; // Promise<User>
// Пример использования
var result: R1 = { id: 1, name: "Test" };
console.log("Тип возвращаемого значения: " + JSON.stringify(result));
Встроенные типы:Встроенные в TypeScript
ReturnType<T>утилитарные типы реализованы с помощью infer.
Извлечение типов параметров функции
Извлечение типов параметров из типа функции.
type FirstParameter<T> = T extends (first: infer P, ...rest: any[]) => any ? P : never;
// Тест
function createUser(name: string, age: number): Пользователь {
return { id: 1, name };
}
function logMessage(msg: string): void {
console.log(msg);
}
type P1 = FirstParameter<typeof createUser>; // string
type P2 = FirstParameter<typeof logMessage>; // string
type P3 = FirstParameter<() => void>; // never
// Пример использования
var nameParam: P1 = "Alice";
console.log("Тип параметра: " + nameParam);
Примечание:Вы можете использовать
...rest: any[]чтобы сопоставить любое количество оставшихся параметров.
Множественный infer
Использование нескольких infer в условном типе.
Пример
type FirstTwo<T> = T extends [infer A, infer B, ...rest: any[]] ? [A, B] : never;
// Тест
type Tuple = [string, number, boolean];
type FirstTwoTypes = FirstTwo<Tuple>; // [string, number]
// Извлечение свойств объекта
type ObjectValue<T> = T extends { value: infer V } ? V : never;
type WithValue = { value: string; name: string };
type ExtractedValue = ObjectValue<WithValue>; // string
// Пример использования
var tupleResult: FirstTwoTypes = ["hello", 123];
console.log("Tuple: " + JSON.stringify(tupleResult));
Кортеж:Использовать
...rest: any[]чтобы сопоставить оставшиеся элементы кортежа.
Использование infer в рекурсивных типах
Используйте infer в утилитах рекурсивных типов для реализации сложных операций над типами.
Пример
type DeepReadonly<T> = T extends Function
? T
: T extends object
? { readonly [P in keyof T]: DeepReadonly<T[P]> }
: T;
// Тест
interface User {
name: string;
address: {
city: string;
zip: string;
};
}
type ReadonlyUser = DeepReadonly<User>;
// Разворачивание типов Promise
type FlattenPromise<T> = T extends Promise<infer U>
? U extends Promise<any>
? FlattenPromise<U>
: U
: T;
type Nested = Promise<Promise<string>>;
type Flat = FlattenPromise<Nested>; // string
// Пример использования
var user: ReadonlyUser = {
name: "Alice",
address: { city: "Beijing", zip: "100000" }
};
// user.name = "Bob"; // Ошибка: readonly
console.log("Глубокий readonly: " + user.name);
Рекурсивный infer:Использование infer в рекурсивных типах позволяет выполнять глубокие преобразования типов, такие как глубокий readonly, глубокий partial и т. д.
Примечания
- Может использоваться только в правой части extends:infer можно использовать только в части extends условного типа
- Объявление переменных типа:Используйте,
infer Vчтобы объявить переменную типа, которая будет выведена. - Разрешено многократное использование:Несколько infer можно использовать в одном условном типе
- Возвращает never при неудачном выводе:Если вывод типа не удаётся, условный тип возвращает never
Лучшие практики:infer — основа реализации пользовательских служебных типов; его освоение открывает мощные операции с типами.
Итог
infer — одна из самых мощных возможностей системы типов TypeScript.
- Извлечение типов:Извлечение частичных типов из сложных типов
- Условный вывод:Выведение типов внутри условных типов
- Связанное с функциями:Извлечение параметров функций и типов возвращаемых значений
- Рекурсивные утилиты:Реализация глубоких преобразований типов
Другие расширенияРекомендация:Глубокое понимание infer позволяет писать более продвинутые утилиты типов, повышая типобезопасность и эффективность разработки.