Lección 92: Conditional Types
🎯 Objetivo: Entender los tipos condicionales (T extends U ? X : Y) y el operador infer para crear tipos que se comportan como funciones lógicas.
🧠 Concepto
1. Sintaxis básica T extends U ? X : Y
Section titled “1. Sintaxis básica T extends U ? X : Y”Un tipo condicional evalúa si un tipo extiende a otro y devuelve un resultado diferente.
💻 Ejemplo
type IsString<T> = T extends string ? true : false;
type A = IsString<"hola">; // truetype B = IsString<123>; // falsetype C = IsString<string>; // truetype D = IsString<number>; // false📝 Lectura: “Si T extiende (es asignable a) U, entonces X; si no, Y.”
2. Tipos condicionales anidados
Section titled “2. Tipos condicionales anidados”Puedes anidar condicionales para crear lógica más compleja.
type TipoResultado<T> = T extends string ? "texto" : T extends number ? "numero" : T extends boolean ? "booleano" : T extends any[] ? "arreglo" : "desconocido";
type R1 = TipoResultado<"hola">; // "texto"type R2 = TipoResultado<42>; // "numero"type R3 = TipoResultado<true>; // "booleano"type R4 = TipoResultado<[1, 2]>; // "arreglo"type R5 = TipoResultado<Date>; // "desconocido"3. Distributive Conditional Types
Section titled “3. Distributive Conditional Types”Cuando un tipo condicional recibe un tipo unión, se distribuye sobre cada miembro.
type ToArray<T> = T extends any ? T[] : never;
type Resultado = ToArray<string | number>;// string[] | number[] (NO (string | number)[])Cómo evitar la distribución
Section titled “Cómo evitar la distribución”type ToArrayNoDistribute<T> = [T] extends [any] ? T[] : never;
type Resultado2 = ToArrayNoDistribute<string | number>;// (string | number)[] — un solo array con la unión🧠 Útil para filtrar miembros de uniones:
type ExcluirString<T> = T extends string ? never : T;
type SinStrings = ExcluirString<string | number | boolean>;// number | boolean
// Existe como utilidad nativa: Exclude<T, U>type SinStrings2 = Exclude<string | number | boolean, string>;// number | boolean
// Extraer solo ciertos tipos: Extract<T, U>type SoloStrings = Extract<string | number | boolean, string>;// string4. El operador infer
Section titled “4. El operador infer”infer permite extraer un tipo de dentro de otro tipo dentro de un condicional.
type ExtraerArray<T> = T extends (infer U)[] ? U : never;
type A = ExtraerArray<number[]>; // numbertype B = ExtraerArray<string[]>; // stringtype C = ExtraerArray<number>; // never (no es array)ReturnType<T> — El ejemplo clásico
Section titled “ReturnType<T> — El ejemplo clásico”type MiReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function saludar(nombre: string): string { return `Hola ${nombre}`;}
type Retorno = MiReturnType<typeof saludar>; // stringParameters<T> con infer
Section titled “Parameters<T> con infer”type MiParameters<T> = T extends (...args: infer P) => any ? P : never;
type Param = MiParameters<(a: string, b: number) => void>;// [a: string, b: number]PromiseType<T> — Extraer tipo de una promesa
Section titled “PromiseType<T> — Extraer tipo de una promesa”type PromiseType<T> = T extends Promise<infer U> ? U : never;
type Result = PromiseType<Promise<string>>; // stringtype Result2 = PromiseType<Promise<number>>; // number5. Aplicaciones prácticas
Section titled “5. Aplicaciones prácticas”Extraer propiedades de un tipo de función
Section titled “Extraer propiedades de un tipo de función”type PrimerArgumento<T> = T extends (arg: infer A, ...rest: any[]) => any ? A : never;
type Arg = PrimerArgumento<(nombre: string, edad: number) => void>;// stringTipos condicionales para APIs
Section titled “Tipos condicionales para APIs”type RespuestaAPI<T> = { exito: boolean; datos: T extends { id: string } ? T : never; error?: string;};
type Usuario = { id: string; nombre: string };type Producto = { sku: string; nombre: string }; // no tiene id string
type RespuestaUsuario = RespuestaAPI<Usuario>;// { exito: boolean; datos: Usuario; error?: string }
type RespuestaProducto = RespuestaAPI<Producto>;// { exito: boolean; datos: never; error?: string }DeepReadonly recursivo
Section titled “DeepReadonly recursivo”type DeepReadonly<T> = { readonly [P in keyof T]: T[P] extends object ? T[P] extends Function ? T[P] : DeepReadonly<T[P]> : T[P];};
type Perfil = { nombre: string; direccion: { calle: string; ciudad: string };};
type PerfilCongelado = DeepReadonly<Perfil>;// {// readonly nombre: string;// readonly direccion: DeepReadonly<{ calle: string; ciudad: string }>;// }⚠️ Consideraciones importantes:
- Los condicionales se evalúan en tiempo de compilación, no en ejecución.
- La distribución ocurre automáticamente con tipos unión. Envuélvelos en
[T]si no deseas distribución. infersolo puede usarse dentro de la parteextendsde un condicional.
📝 Resumen: Los tipos condicionales son el equivalente a if/else en el sistema de tipos de TypeScript. Con infer puedes extraer y reutilizar tipos desde otros tipos, lo que permite crear utilidades de tipos extremadamente flexibles.