Domina la distributividad de los tipos condicionales, el pattern matching con infer y casos límite como never, any y boolean.
Abrir esta lección en KodokonLos tipos condicionales son el mecanismo de bifurcación del sistema de tipos: T extends U ? X : Y pregunta al compilador si T es asignable a U y luego selecciona una rama. Todas las utilidades estándar como ReturnType, Exclude o Awaited se construyen sobre ellos. En este nivel, la pregunta ya no es cómo escribirlos, sino cómo predecir exactamente cómo los evalúa el compilador, especialmente cuando intervienen uniones y never.
type IsString<T> = T extends string ? true : false;
type A = IsString<"hello">;
// true
type B = IsString<string | number>;
// boolean, not false!El comportamiento clave es la distributividad: cuando el tipo probado es un parámetro de tipo desnudo (no envuelto en una tupla, un array o una intersección), el condicional se aplica por separado a cada miembro de la unión y luego se combinan los resultados. Por eso IsString<string | number> calcula true | false, que es boolean. Para desactivar esta distribución, envuelve ambos lados en una tupla: [T] extends [U]. Un caso límite temible: never es la unión vacía; distribuir sobre cero miembros no evalúa ninguna rama en absoluto, y el resultado es never sin importar qué rama hayas escrito.
type ToArray<T> = T extends unknown ? T[] : never;
type NoDist<T> = [T] extends [unknown] ? T[] : never;
type A = ToArray<string | number>;
// string[] | number[]
type B = NoDist<string | number>;
// (string | number)[]
type IsNever<T> = [T] extends [never] ? true : false;
type C = IsNever<never>; // true thanks to the tupleLa palabra clave infer introduce una variable de tipo capturada mediante pattern matching: el compilador intenta hacer coincidir la estructura del tipo probado con el patrón y liga la variable si la coincidencia tiene éxito. Puedes colocar varias declaraciones infer con el mismo nombre: en una posición covariante (valores producidos, como un tipo de retorno), los candidatos se fusionan en una unión; en una posición contravariante (parámetros de función), se combinan en una intersección. Esta regla se deriva directamente de la varianza: es la única opción segura en cada dirección.
type ElementOf<T> =
T extends readonly (infer E)[] ? E : never;
type FirstParam<T> =
T extends (arg: infer P) => unknown ? P : never;
type Merged<T> =
T extends { a: (x: infer P) => void;
b: (x: infer P) => void }
? P : never;
type M = Merged<{
a: (x: { id: string }) => void;
b: (x: { tag: string }) => void;
}>; // { id: string } & { tag: string }Desde TypeScript 4.7, infer acepta una restricción en línea: infer N extends number filtra la captura y, desde 4.8, el compilador incluso convierte un literal de cadena numérica en un literal de número. Otra sutileza de la especificación: un tipo condicional cuya entrada todavía contiene un parámetro genérico sin resolver permanece diferido; solo se evalúa en el sitio de instanciación, lo que explica por qué algunos errores solo aparecen en el lado de quien lo llama.
type ToNumber<S extends string> =
S extends `${infer N extends number}` ? N : never;
type Port = ToNumber<"8080">; // the literal 8080
type Bad = ToNumber<"abc">; // neverExclude<boolean, true>?type IsNever<T> = T extends never ? true : false;, ¿qué es IsNever<never>?infer P con el mismo nombre colocados en posición de parámetro de función producen: