Skip to content

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

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">; // true
type B = IsString<123>; // false
type C = IsString<string>; // true
type D = IsString<number>; // false

📝 Lectura: “Si T extiende (es asignable a) U, entonces X; si no, Y.”


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"

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)[])
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>;
// string

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[]>; // number
type B = ExtraerArray<string[]>; // string
type C = ExtraerArray<number>; // never (no es array)
type MiReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function saludar(nombre: string): string {
return `Hola ${nombre}`;
}
type Retorno = MiReturnType<typeof saludar>; // string
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>>; // string
type Result2 = PromiseType<Promise<number>>; // number

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>;
// string
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 }
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.
  • infer solo puede usarse dentro de la parte extends de 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.