Una gu铆a completa sobre la palabra clave 'infer' de TypeScript, explicando c贸mo usarla con tipos condicionales para la extracci贸n y manipulaci贸n de tipos, incluyendo casos de uso avanzados.
Dominando TypeScript Infer: Extracci贸n de Tipos Condicionales para la Manipulaci贸n Avanzada de Tipos
El sistema de tipos de TypeScript es incre铆blemente potente, lo que permite a los desarrolladores crear aplicaciones robustas y mantenibles. Una de las caracter铆sticas clave que permite esta potencia es la palabra clave infer utilizada junto con tipos condicionales. Esta combinaci贸n proporciona un mecanismo para extraer tipos espec铆ficos de estructuras de tipos complejas. Esta publicaci贸n de blog profundiza en la palabra clave infer, explicando su funcionalidad y mostrando casos de uso avanzados. Exploraremos ejemplos pr谩cticos aplicables a diversos escenarios de desarrollo de software, desde la interacci贸n con la API hasta la manipulaci贸n compleja de estructuras de datos.
驴Qu茅 son los Tipos Condicionales?
Antes de sumergirnos en infer, revisemos r谩pidamente los tipos condicionales. Los tipos condicionales en TypeScript te permiten definir un tipo basado en una condici贸n, similar a un operador ternario en JavaScript. La sintaxis b谩sica es:
T extends U ? X : Y
Esto se lee como: "Si el tipo T es asignable al tipo U, entonces el tipo es X; de lo contrario, el tipo es Y".
Ejemplo:
type IsString<T> = T extends string ? true : false;
type StringResult = IsString<string>; // type StringResult = true
type NumberResult = IsString<number>; // type NumberResult = false
Introducci贸n a la Palabra Clave infer
La palabra clave infer se usa dentro de la cl谩usula extends de un tipo condicional para declarar una variable de tipo que se puede inferir del tipo que se est谩 comprobando. En esencia, te permite "capturar" una parte de un tipo para usarla m谩s tarde.
Sintaxis B谩sica:
type MyType<T> = T extends (infer U) ? U : never;
En este ejemplo, si T es asignable a alg煤n tipo, TypeScript intentar谩 inferir el tipo de U. Si la inferencia tiene 茅xito, el tipo ser谩 U; de lo contrario, ser谩 never.
Ejemplos Sencillos de infer
1. Inferir el Tipo de Retorno de una Funci贸n
Un caso de uso com煤n es inferir el tipo de retorno de una funci贸n:
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
function add(a: number, b: number): number {
return a + b;
}
type AddReturnType = ReturnType<typeof add>; // type AddReturnType = number
function greet(name: string): string {
return `Hola, ${name}!`;
}
type GreetReturnType = ReturnType<typeof greet>; // type GreetReturnType = string
En este ejemplo, ReturnType<T> toma un tipo de funci贸n T como entrada. Verifica si T es asignable a una funci贸n que acepta cualquier argumento y devuelve un valor. Si lo es, infiere el tipo de retorno como R y lo devuelve. De lo contrario, devuelve any.
2. Inferir el Tipo de Elemento de un Array
Otro escenario 煤til es extraer el tipo de elemento de un array:
type ArrayElementType<T> = T extends (infer U)[] ? U : never;
type NumberArrayType = ArrayElementType<number[]>; // type NumberArrayType = number
type StringArrayType = ArrayElementType<string[]>; // type StringArrayType = string
type MixedArrayType = ArrayElementType<(string | number)[]>; // type MixedArrayType = string | number
type NotAnArrayType = ArrayElementType<number>; // type NotAnArrayType = never
Aqu铆, ArrayElementType<T> verifica si T es un tipo de array. Si lo es, infiere el tipo de elemento como U y lo devuelve. Si no, devuelve never.
Casos de Uso Avanzados de infer
1. Inferir los Par谩metros de un Constructor
Puedes usar infer para extraer los tipos de par谩metro de una funci贸n constructora:
type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;
class Persona {
constructor(public name: string, public age: number) {}
}
type PersonConstructorParams = ConstructorParameters<typeof Persona>; // type PersonConstructorParams = [string, number]
class Punto {
constructor(public x: number, public y: number) {}
}
type PointConstructorParams = ConstructorParameters<typeof Punto>; // type PointConstructorParams = [number, number]
En este caso, ConstructorParameters<T> toma un tipo de funci贸n constructora T. Infiere los tipos de los par谩metros del constructor como P y los devuelve como una tupla.
2. Extraer Propiedades de Tipos de Objeto
infer tambi茅n se puede usar para extraer propiedades espec铆ficas de tipos de objeto usando tipos mapeados y tipos condicionales:
type PickByType<T, K extends keyof T, U> = {
[P in K as T[P] extends U ? P : never]: T[P];
};
interface Usuario {
id: number;
nombre: string;
edad: number;
correoElectronico: string;
activo: boolean;
}
type StringProperties = PickByType<Usuario, keyof Usuario, string>; // type StringProperties = { nombre: string; correoElectronico: string; }
type NumberProperties = PickByType<Usuario, keyof Usuario, number>; // type NumberProperties = { id: number; edad: number; }
//Una interfaz que representa coordenadas geogr谩ficas.
interface CoordenadasGeograficas {
latitud: number;
longitud: number;
altitud: number;
pais: string;
ciudad: string;
zonaHoraria: string;
}
type NumberCoordinateProperties = PickByType<CoordenadasGeograficas, keyof CoordenadasGeograficas, number>; // type NumberCoordinateProperties = { latitud: number; longitud: number; altitud: number; }
Aqu铆, PickByType<T, K, U> crea un nuevo tipo que incluye solo las propiedades de T (con claves en K) cuyos valores son asignables al tipo U. El tipo mapeado itera sobre las claves de T, y el tipo condicional filtra las claves que no coinciden con el tipo especificado.
3. Trabajando con Promesas
Puedes inferir el tipo resuelto de una Promise:
type Awaited<T> = T extends Promise<infer U> ? U : T;
async function fetchData(): Promise<string> {
return 'Datos de la API';
}
type FetchDataType = Awaited<ReturnType<typeof fetchData>>; // type FetchDataType = string
async function fetchNumbers(): Promise<number[]> {
return [1, 2, 3];
}
type FetchedNumbersType = Awaited<ReturnType<typeof fetchNumbers>>; //type FetchedNumbersType = number[]
El tipo Awaited<T> toma un tipo T, que se espera que sea una Promesa. Luego, el tipo infiere el tipo resuelto U de la Promesa y lo devuelve. Si T no es una promesa, devuelve T. Este es un tipo de utilidad incorporado en las versiones m谩s recientes de TypeScript.
4. Extracci贸n del Tipo de un Array de Promesas
Combinar Awaited y la inferencia de tipo de array te permite inferir el tipo resuelto por un array de Promesas. Esto es particularmente 煤til cuando se trata con Promise.all.
type PromiseArrayReturnType<T extends Promise<any>[]> = {
[K in keyof T]: Awaited<T[K]>;
};
async function getUSDRate(): Promise<number> {
return 0.0069;
}
async function getEURRate(): Promise<number> {
return 0.0064;
}
const rates = [getUSDRate(), getEURRate()];
type RatesType = PromiseArrayReturnType<typeof rates>;
// type RatesType = [number, number]
Este ejemplo define primero dos funciones as铆ncronas, getUSDRate y getEURRate, que simulan la obtenci贸n de tipos de cambio. Luego, el tipo de utilidad PromiseArrayReturnType extrae el tipo resuelto de cada Promise en el array, lo que resulta en un tipo de tupla donde cada elemento es el tipo esperado de la Promesa correspondiente.
Ejemplos Pr谩cticos en Diferentes Dominios
1. Aplicaci贸n de Comercio Electr贸nico
Considera una aplicaci贸n de comercio electr贸nico donde obtienes los detalles del producto de una API. Puedes usar infer para extraer el tipo de los datos del producto:
interface Producto {
id: number;
nombre: string;
precio: number;
descripcion: string;
imagenUrl: string;
categoria: string;
calificacion: number;
paisDeOrigen: string;
}
async function fetchProduct(productId: number): Promise<Producto> {
// Simular llamada a la API
return new Promise((resolve) => {
setTimeout(() => {
resolve({
id: productId,
nombre: 'Producto de Ejemplo',
precio: 29.99,
descripcion: 'Un producto de muestra',
imagenUrl: 'https://example.com/image.jpg',
categoria: 'Electr贸nicos',
calificacion: 4.5,
paisDeOrigen: 'Canad谩'
});
}, 500);
});
}
type ProductType = Awaited<ReturnType<typeof fetchProduct>>; // type ProductType = Producto
function displayProductDetails(product: ProductType) {
console.log(`Nombre del Producto: ${product.nombre}`);
console.log(`Precio: ${product.precio} ${product.paisDeOrigen === 'Canad谩' ? 'CAD' : (product.paisDeOrigen === 'USA' ? 'USD' : 'EUR')}`);
}
fetchProduct(123).then(displayProductDetails);
En este ejemplo, definimos una interfaz Producto y una funci贸n fetchProduct que obtiene los detalles del producto de una API. Usamos Awaited y ReturnType para extraer el tipo Producto del tipo de retorno de la funci贸n fetchProduct, lo que nos permite verificar el tipo de la funci贸n displayProductDetails.
2. Internacionalizaci贸n (i18n)
Sup贸n que tienes una funci贸n de traducci贸n que devuelve diferentes cadenas seg煤n la configuraci贸n regional. Puedes usar infer para extraer el tipo de retorno de esta funci贸n para la seguridad de tipos:
interface Traducciones {
saludo: string;
despedida: string;
mensajeDeBienvenida: (nombre: string) => string;
}
const enTranslations: Traducciones = {
saludo: 'Hello',
despedida: 'Goodbye',
mensajeDeBienvenida: (name: string) => `Welcome, ${name}!`,
};
const frTranslations: Traducciones = {
saludo: 'Bonjour',
despedida: 'Au revoir',
mensajeDeBienvenida: (name: string) => `Bienvenue, ${name}!`,
};
function getTranslation(locale: 'en' | 'fr'): Traducciones {
return locale === 'en' ? enTranslations : frTranslations;
}
type TranslationType = ReturnType<typeof getTranslation>;
function greetUser(locale: 'en' | 'fr', name: string) {
const translations = getTranslation(locale);
console.log(translations.mensajeDeBienvenida(name));
}
greetUser('fr', 'Jean'); // Output: Bienvenue, Jean!
Aqu铆, el TranslationType se infiere como la interfaz Traducciones, asegurando que la funci贸n greetUser tenga la informaci贸n de tipo correcta para acceder a las cadenas traducidas.
3. Manejo de Respuestas de API
Al trabajar con API, la estructura de la respuesta puede ser compleja. infer puede ayudar a extraer tipos de datos espec铆ficos de respuestas de API anidadas:
interface ApiResponse<T> {
estado: number;
datos: T;
mensaje?: string;
}
interface UserData {
id: number;
nombreDeUsuario: string;
correoElectronico: string;
perfil: {
nombre: string;
apellido: string;
pais: string;
idioma: string;
}
}
async function fetchUser(userId: number): Promise<ApiResponse<UserData>> {
// Simular llamada a la API
return new Promise((resolve) => {
setTimeout(() => {
resolve({
estado: 200,
datos: {
id: userId,
nombreDeUsuario: 'johndoe',
correoElectronico: 'john.doe@example.com',
perfil: {
nombre: 'John',
apellido: 'Doe',
pais: 'USA',
idioma: 'en'
}
}
});
}, 500);
});
}
type UserApiResponse = Awaited<ReturnType<typeof fetchUser>>;
type UserProfileType = UserApiResponse['datos']['perfil'];
function displayUserProfile(profile: UserProfileType) {
console.log(`Nombre: ${profile.nombre} ${profile.apellido}`);
console.log(`Pa铆s: ${profile.pais}`);
}
fetchUser(123).then((response) => {
if (response.estado === 200) {
displayUserProfile(response.datos.perfil);
}
});
En este ejemplo, definimos una interfaz ApiResponse y una interfaz UserData. Usamos infer e indexaci贸n de tipos para extraer el UserProfileType de la respuesta de la API, asegurando que la funci贸n displayUserProfile reciba el tipo correcto.
Mejores Pr谩cticas para Usar infer
- Mantenlo Simple: Usa
infersolo cuando sea necesario. Usarlo en exceso puede dificultar la lectura y comprensi贸n de tu c贸digo. - Documenta Tus Tipos: Agrega comentarios para explicar qu茅 est谩n haciendo tus tipos condicionales y declaraciones
infer. - Prueba Tus Tipos: Usa la comprobaci贸n de tipos de TypeScript para asegurarte de que tus tipos se comportan como se espera.
- Considera el Rendimiento: Los tipos condicionales complejos a veces pueden afectar el tiempo de compilaci贸n. Ten en cuenta la complejidad de tus tipos.
- Usa Tipos de Utilidad: TypeScript proporciona varios tipos de utilidad integrados (por ejemplo,
ReturnType,Awaited) que pueden simplificar tu c贸digo y reducir la necesidad de declaracionesinferpersonalizadas.
Posibles Problemas Comunes
- Inferencia Incorrecta: A veces, TypeScript podr铆a inferir un tipo que no es lo que esperas. Vuelve a comprobar tus definiciones y condiciones de tipo.
- Dependencias Circulares: Ten cuidado al definir tipos recursivos usando
infer, ya que pueden conducir a dependencias circulares y errores de compilaci贸n. - Tipos Demasiado Complejos: Evita crear tipos condicionales demasiado complejos que sean dif铆ciles de entender y mantener. Div铆delos en tipos m谩s peque帽os y manejables.
Alternativas a infer
Si bien infer es una herramienta poderosa, existen situaciones en las que los enfoques alternativos podr铆an ser m谩s apropiados:
- Aserciones de Tipo: En algunos casos, puedes usar aserciones de tipo para especificar expl铆citamente el tipo de un valor en lugar de inferirlo. Sin embargo, ten cuidado con las aserciones de tipo, ya que pueden omitir la comprobaci贸n de tipos.
- Guardias de Tipo: Las guardias de tipo se pueden usar para restringir el tipo de un valor en funci贸n de comprobaciones en tiempo de ejecuci贸n. Esto es 煤til cuando necesitas manejar diferentes tipos basados en condiciones en tiempo de ejecuci贸n.
- Tipos de Utilidad: TypeScript proporciona un amplio conjunto de tipos de utilidad que pueden manejar muchas tareas comunes de manipulaci贸n de tipos sin la necesidad de declaraciones
inferpersonalizadas.
Conclusi贸n
La palabra clave infer en TypeScript, cuando se combina con tipos condicionales, desbloquea capacidades avanzadas de manipulaci贸n de tipos. Te permite extraer tipos espec铆ficos de estructuras de tipos complejas, lo que te permite escribir c贸digo m谩s robusto, mantenible y seguro para tipos. Desde la inferencia de tipos de retorno de funciones hasta la extracci贸n de propiedades de tipos de objetos, las posibilidades son vastas. Al comprender los principios y las mejores pr谩cticas descritas en esta gu铆a, puedes aprovechar infer al m谩ximo y elevar tus habilidades de TypeScript. Recuerda documentar tus tipos, probarlos a fondo y considerar enfoques alternativos cuando sea apropiado. Dominar infer te permite escribir c贸digo TypeScript verdaderamente expresivo y poderoso, lo que en 煤ltima instancia conduce a un mejor software.