Concepto 6 de 12
Tipos condicionales
Un tipo condicional es el `if` a nivel de tipos. La pregunta que plantea es siempre la misma: ¿es el lado izquierdo asignable al derecho? No «¿son iguales?», no «¿es una instancia de?» — asignable, y en un solo sentido.
Asignabilidad, no igualdad
type A = 'hello' extends string ? true : false // true type B = string extends 'hello' ? true : false // false type C = never extends string ? true : false // true — never fits anywhere type D = { a: 1; b: 2 } extends { a: 1 } ? true : false // true
Un tipo más estrecho es asignable a uno más amplio, así que la prueba tiene dirección. Un objeto con propiedades de más es asignable a otro con menos, y por eso `extends` no sirve para comprobar que dos tipos objeto tengan la misma forma.
Cuando de verdad necesitas «el mismo tipo», necesitas el helper `Equal` sobre el que se apoya cada comprobación de este sitio — dos condicionales diferidos comparados por identidad, más estricto que la asignabilidad en ambos sentidos.
Las ramas se prueban en orden
type Describe<T> = T extends (...args: never[]) => unknown ? 'function' : T extends unknown[] ? 'array' : T extends object ? 'object' : 'primitive'
Los casos más estrechos deben ir primero. Funciones y arrays son ambos objetos, así que probar `extends object` pronto se los tragaría — la causa clásica de un `DeepReadonly` que destruye en silencio cada función que toca.
El error más común
// Intent: is T exactly string? type IsString<T> = T extends string ? true : false IsString<'hello'> // true — but 'hello' is not string, it is narrower IsString<any> // boolean — any matches both branches at once
`extends` responde a «cabe en», así que todo literal de cadena pasa una prueba de `string`. Y `any` es asignable a todo, así que un condicional con `any` devuelve la unión de ambas ramas — un resultado que casi nunca es el que se pretendía.
Para recordar
Los condicionales preguntan por asignabilidad y prueban las ramas en orden. Lo estrecho primero, y recurre a `Equal` en cuanto necesites igualdad real.