La palabra clave infer de TypeScript

infer es una palabra clave en los tipos condicionales de TypeScript, utilizada para inferir nuevos tipos a partir de tipos existentes.

Permite extraer y deducir tipos en tipos condicionales genéricos, logrando potentes operaciones de tipos.


Diagrama SVG: cómo funciona infer Fondo Título Principio de funcionamiento de la palabra clave infer Tipo original Tipo original type Promise<T> = { value: T } Flecha Deducción con infer Resultado de la inferencia Tipo extraído type ValueOf<T> = T extends Promise<infer V> ? V : never Ejemplo Ejemplo ValueOf<Promise<string>> → string Parte inferior: escenarios de aplicación Escenarios de aplicación comunes de infer Escenario 1 Extraer el tipo de valor de Promise Escenario 2 Extraer el tipo de elemento de un array Escenario 3 Extraer el tipo de retorno de una función Etiqueta de flecha

¿Por qué se necesita la palabra clave infer?

En programación genérica, a menudo necesitamos extraer tipos parciales de tipos complejos.

Por ejemplo, dePromise<string>se extraestring, deArray<User>se extraeUser。

La palabra clave infer hace que esta extracción de tipos sea elegante y segura en cuanto a tipos; permite declarar una variable de tipo en un tipo condicional y usarla en la rama true.

Explicación del concepto:infer proviene de la palabra inglesa 'infer', que significa 'inferir'. Solo se puede usar en la cláusula extends de un tipo condicional para declarar una variable de tipo que se va a inferir.


Uso básico

Usa infer en tipos condicionales para inferir tipos.

Ejemplo

// Extraer el tipo de valor de Promise
// Si T es Promise<V>, devuelve V; de lo contrario, devuelve never
type ValueOf<T> = T extends Promise<infer V> ? V : never;

// Prueba
type Str = ValueOf<Promise<string>>;  // string
type Num = ValueOf<Promise<number>>; // number
type NotPrm = ValueOf<string>;        // never

// Ejemplo de uso
var promise: Promise<string> = Promise.resolve("hello");
var value: ValueOf<typeof promise> = "world";

console.log("Extracción de cadena: " + (typeof value === "string" ? "Éxito" : "Fallo"));

Resultado de la ejecución:

Extracción de cadenas: Éxito

Explicación:infer VSe declara una variable de tipo V, y TypeScript infiere automáticamente el tipo de V.


Extraer el tipo de elemento de un array

Extrae el tipo de elemento de un tipo de array.

Ejemplo

// Extraer el tipo de elemento del array
// Si T es un array V[], devuelve V
type ArrayElement<T> = T extends (infer V)[] ? V : never;

// Prueba
type User = { name: string };
type Users = User[];

type E1 = ArrayElement<string[]>;   // string
type E2 = ArrayElement<Users>;       // User
type E3 = ArrayElement<number>;       // never (no es un array)

// Ejemplo de uso
var users: Users = [{ name: "Alice" }];
var element: ArrayElement<Users> = users[0];

console.log("Tipo de elemento: " + element.name);

Aplicación:Este patrón se usa a menudo para extraer elementos de un tipo específico de un tipo unión.


Extraer el tipo de retorno de una función

Extrae el tipo de retorno de un tipo de función.

Ejemplo

// Extraer el tipo de retorno de la función
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

// Prueba
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>

// Ejemplo de uso
var result: R1 = { id: 1, name: "Test" };
console.log("Tipo de retorno: " + JSON.stringify(result));

Tipos integrados:El tipo de utilidad se implementa usando infer.ReturnType<T>Extraer el tipo de parámetros de una función


Extraer el tipo de parámetros de una función

Ejemplo

<h2 class="example">Ejemplo
// Extraer el tipo del primer parámetro de la función
type FirstParameter<T> = T extends (first: infer P, ...rest: any[]) => any ? P : never;

// Prueba
function createUser(name: string, age: number): User {
    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

// Ejemplo de uso
var nameParam: P1 = "Alice";
console.log("Tipo de parámetro: " + nameParam);

Nota:Se puede usar...rest: any[]para coincidir con cualquier número de parámetros restantes.


Múltiples infer

Usar múltiples infer en un tipo condicional.

Ejemplo

// Extraer los tipos de los dos primeros elementos de la tupla
type FirstTwo<T> = T extends [infer A, infer B, ...rest: any[]] ? [A, B] : never;

// Prueba
type Tuple = [string, number, boolean];
type FirstTwoTypes = FirstTwo<Tuple>; // [string, number]

// Extraer propiedades de objeto
type ObjectValue<T> = T extends { value: infer V } ? V : never;

type WithValue = { value: string; name: string };
type ExtractedValue = ObjectValue<WithValue>; // string

// Ejemplo de uso
var tupleResult: FirstTwoTypes = ["hello", 123];
console.log("Tupla: " + JSON.stringify(tupleResult));

Tupla:El uso de...rest: any[]permite coincidir con los elementos restantes de la tupla.


Usar infer en tipos recursivos

Usar infer en herramientas de tipos recursivos para implementar operaciones de tipos complejas.

Ejemplo

// Tipo de solo lectura profunda: aplicación recursiva de infer
type DeepReadonly<T> = T extends Function
    ? T
    : T extends object
        ? { readonly [P in keyof T]: DeepReadonly<T[P]> }
        : T;

// Prueba
interface User {
    name: string;
    address: {
        city: string;
        zip: string;
    };
}

type ReadonlyUser = DeepReadonly<User>;

// Aplanar el tipo 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

// Ejemplo de uso
var user: ReadonlyUser = {
    name: "Alice",
    address: { city: "Beijing", zip: "100000" }
};
// user.name = "Bob"; // Error: solo lectura
console.log("Solo lectura profunda: " + user.name);

infer recursivo:Usar infer en tipos recursivos permite implementar conversiones de tipos profundas, como solo lectura profunda, opcional profundo, etc.


Precauciones

  • Solo se puede usar en el lado derecho de extends:infer solo se puede usar en la cláusula extends de un tipo condicional
  • Declarar variable de tipo:Usarinfer Vpara declarar una variable de tipo pendiente de inferir
  • Se pueden usar múltiples:Se pueden usar múltiples infer en un tipo condicional
  • Si la inferencia falla, devuelve never:Si la inferencia falla, el tipo condicional devuelve never

Mejores prácticas:infer es el núcleo para implementar tipos de utilidad personalizados; dominarlo permite lograr potentes operaciones de tipos.


Resumen

infer es una de las características más potentes del sistema de tipos de TypeScript.

  • Extracción de tipos:Extraer tipos parciales de tipos complejos
  • Inferencia condicional:Inferir tipos en tipos condicionales
  • Relacionado con funciones:Extraer parámetros de función y tipos de retorno
  • Herramientas recursivas:Implementar conversiones de tipos profundas

Sugerencia:Comprender a fondo infer te permite escribir herramientas de tipos más avanzadas, mejorando la seguridad de tipos y la eficiencia de desarrollo.

Otras extensiones