Tipos Condicionales
En el corazón de la mayoría de los programas útiles, tenemos que tomar decisiones basadas en las entradas. Los programas JavaScript no son diferentes, pero dado que los valores pueden ser fácilmente introspeccionados, esas decisiones también se basan en los tipos de entradas. Los tipos condicionales ayudan a describir la relación entre los tipos de entradas y salidas.
Los tipos condicionales toman una forma que se parece un poco a las expresiones condicionales (condition ? trueExpression : falseExpression
) en JavaScript:
Cuando el tipo de la izquierda de extends
se puede asignar al de la derecha, obtendrás el tipo en la primera rama (la rama “verdadera”); de lo contrario, obtendrás el tipo en la última rama (la rama “falsa”).
De los ejemplos anteriores, los tipos condicionales pueden no parecer útiles de inmediato; ¡podemos decirnos que Dog extends Animal
y elegir number
o string
!
Pero el poder de los tipos condicionales proviene de su uso con generics.
Por ejemplo, tomemos la siguiente función createLabel
:
Estas sobrecargas para createLabel
describen una única función de JavaScript que realiza una elección según los tipos de sus entradas. Ten en cuenta algunas cosas:
- Si una biblioteca tiene que hacer el mismo tipo de elección una y otra vez en toda su API, esto se vuelve engorroso.
- Tenemos que crear tres sobrecargas: una para cada caso en el que estemos seguros del tipo (una para
string
y otra paranumber
), y otra para el caso más general (tomando unstring | number
). Por cada nuevo tipo quecreateLabel
puede manejar, el número de sobrecargas crece exponencialmente.
En lugar de eso, podemos codificar esa lógica en un tipo condicional:
Luego podemos usar ese tipo condicional para simplificar nuestras sobrecargas a una sola función sin sobrecargas.
Restricciones de tipo condicional
A menudo, las comprobaciones de tipo condicional nos proporcionarán información nueva. Así como el estrechamiento con protecciones de tipo puede darnos un tipo más específico, la rama verdadera de un tipo condicional restringirá aún más los generics según el tipo que comparamos.
Por ejemplo, tomemos lo siguiente:
En este ejemplo, se producen errores de TypeScript porque no se sabe si T
tiene una propiedad llamada message
.
Podríamos restringir T
y TypeScript ya no se quejaría:
Sin embargo, ¿qué pasaría si quisiéramos que MessageOf
tomara cualquier tipo y que el valor predeterminado fuera algo como never
si una propiedad message
no está disponible?
Podemos hacer esto eliminando la restricción e introduciendo un tipo condicional:
Dentro de la rama verdadera, TypeScript sabe que T
tendrá una propiedad message
.
Como otro ejemplo, también podríamos escribir un tipo llamado Flatten
que aplana los tipos de arrays a sus tipos de elementos, pero de lo contrario los deja tal cual:
Cuando a Flatten
se le asigna un tipo de array, utiliza un acceso indexado con number
para obtener el tipo de elemento de string[]
.
De lo contrario, simplemente devuelve el tipo que se le proporcionó.
Inferir dentro de tipos condicionales
Nos encontramos usando tipos condicionales para aplicar restricciones y luego extraer tipos. Esta termina siendo una operación tan común que los tipos condicionales la hacen más fácil.
Los tipos condicionales nos brindan una manera de inferir a partir de los tipos que comparamos en la rama verdadera usando la palabra clave infer
.
Por ejemplo, podríamos haber inferido el tipo de elemento en Flatten
en lugar de recuperarlo “manualmente” con un tipo de acceso indexado:
Aquí usamos la palabra clave infer
para introducir declarativamente una nueva variable de tipo genérico llamada Item
en lugar de especificar cómo recuperar el tipo de elemento de Type
dentro de la rama verdadera.
Esto nos libera de tener que pensar en cómo profundizar y sondear la estructura de los tipos que nos interesan.
Podemos escribir algunos alias de tipos de ayuda útiles usando la palabra clave infer
.
Por ejemplo, para casos simples, podemos extraer el tipo de retorno de los tipos de funciones:
Cuando se infiere a partir de un tipo con múltiples firmas de llamada (como el tipo de una función sobrecargada), las inferencias se hacen a partir de la última firma (que, presumiblemente, es el caso más permisivo). No es posible realizar una resolución de sobrecarga basada en una lista de tipos de argumentos.
Tipos condicionales distributivos
Cuando los tipos condicionales actúan sobre un tipo genérico, se vuelven distributivos cuando se les da un tipo de unión. Por ejemplo, toma lo siguiente:
Si conectamos un tipo de unión en ToArray
, entonces el tipo condicional se aplicará a cada miembro de esa unión.
Lo que sucede aquí es que ToArray
se distribuye en:
y mapea cada tipo de miembro de la unión, a lo que es efectivamente:
lo cual nos deja con:
Normalmente, la distributividad es el comportamiento deseado.
Para evitar ese comportamiento, puedes rodear cada lado de la palabra clave extends
entre corchetes.