Ключевое слово infer в TypeScript

infer — это ключевое слово в условных типах TypeScript, используемое для вывода новых типов из существующих типов.

Оно позволяет извлекать и выводить типы внутри обобщённых условных типов, обеспечивая мощные операции над типами.


SVG-диаграмма: как работает infer Background Title How the infer keyword works Original Type Original Type type Promise<T> = { value: T } Arrow infer inference Inference result Extracted type type ValueOf<T> = T extends Promise<infer V> ? V : never Example Example ValueOf<Promise<string>> → string Lower half: Application scenarios Common use cases for infer Scenario 1 Extract Promise value type Scenario 2 Extract array element type Scenario 3 Extract function return type Arrow marker

Зачем нужно ключевое слово infer?

В обобщённом программировании нам часто нужно извлекать частичные типы из сложных типов.

Например, изPromise<string>extractstring, fromArray<User>extractUser。

Ключевое слово infer делает такое извлечение типов элегантным и типобезопасным. Оно позволяет объявить переменную типа в условном типе, а затем использовать её в ветке true.

Пояснение понятия:infer — это сокращение от "infer", означающее "выводить". Его можно использовать только в части extends условного типа, чтобы объявить переменную типа, которую нужно вывести.


Базовое использование

Используйте infer для вывода типов в условных типах.

Пример

// Извлечь тип значения из Promise
// Если 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));

Встроенные типы:Встроенные в TypeScriptReturnType<T>утилитарные типы реализованы с помощью infer.


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

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

< h2 class="example">Пример
// Извлечь тип первого параметра функции
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 в утилитах рекурсивных типов для реализации сложных операций над типами.

Пример

// Глубокий readonly тип - рекурсивное применение 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 позволяет писать более продвинутые утилиты типов, повышая типобезопасность и эффективность разработки.

Другие расширения