TypeScript le permite automatizar muchas tareas que, sin usar este lenguaje, los desarrolladores deben resolver de forma independiente. Pero cuando se trabaja con TypeScript, no es necesario usar constantemente anotaciones de tipo. El hecho es que el compilador hace un gran trabajo de inferencia de tipos en función del contexto de ejecución del código. El artículo, cuya traducción publicamos hoy, está dedicado a casos bastante complicados de inferencia de tipos en los que infer
se utilizan la palabra clave y la construcción as const
.
Fundamentos de inferencia de tipos
Primero, eche un vistazo al ejemplo de inferencia de tipo más simple.let variable;
Una variable que se declara de esta manera es de tipo any
. No le dimos al compilador ninguna pista sobre cómo lo usaremos.let variable = 'Hello!';
Aquí declaramos una variable e inmediatamente escribimos un valor en ella. TypeScript ahora puede adivinar que esta variable es de tipo string
, por lo que ahora tenemos una variable de tipo perfectamente aceptable.Un enfoque similar se aplica a las funciones:function getRandomInteger(max: number) {
return Math.floor(Math.random() * max);
}
En este código, no indicamos que la función getRandomInteger
devuelva un número. Pero el compilador de TypeScript lo sabe muy bien.Inferencia de tipos en genéricos
Los conceptos anteriores están relacionados con los tipos universales (genéricos). Si desea saber más sobre los genéricos, eche un vistazo a este y este material.Al crear tipos genéricos, puede hacer muchas cosas útiles. La inferencia de tipos hace que trabajar con tipos universales sea más conveniente y lo simplifica.function getProperty<ObjectType, KeyType extends keyof ObjectType>(
object: ObjectType, key: KeyType
) {
return object[key];
}
Al usar la función genérica anterior, no necesitamos especificar explícitamente los tipos.const dog = {
name: 'Fluffy'
};
getProperty(dog, 'name');
Esta técnica, entre otras cosas, es muy útil para crear componentes React universales. Aquí está el material al respecto.Usando la palabra clave infer
Una de las características más avanzadas de TypeScript que viene a la mente cuando se habla de inferencia de tipos es la palabra clave infer
.Considera un ejemplo. Crea la siguiente función:function call<ReturnType>(
functionToCall: (...args: any[]) => ReturnType, ...args: any[]
): ReturnType {
return functionToCall(...args);
}
Con la ayuda de esta función, llame a otra función y escriba lo que devuelve en una constante:const randomNumber = call(getRandomInteger, 100);
La expresión anterior nos permite obtener lo que la función getRandomInteger
que recibió la entrada devolvió cuando el límite superior del entero aleatorio regresó a ella, 100. Es cierto, hay un pequeño problema. Se basa en el hecho de que nada nos impide ignorar los tipos de argumentos de función getRandomInteger
.const randomNumber = call(getRandomInteger, '100');
Dado que TypeScript admite parámetros de dispersión y descanso en funciones de orden superior, podemos resolver este problema de la siguiente manera:function call<ArgumentsType extends any[], ReturnType>(
functionToCall: (...args: ArgumentsType) => ReturnType, ...args: ArgumentsType
): ReturnType {
return functionToCall(...args);
}
Ahora hemos señalado que la función call
puede procesar una matriz de argumentos en cualquier forma, y también que los argumentos deben coincidir con las expectativas de la función que se le pasa.Ahora intentemos nuevamente hacer una llamada de función incorrecta:const randomNumber = call(getRandomInteger, '100');
Esto da como resultado un mensaje de error:Argument of type ‘”100″‘ is not assignable to parameter of type ‘number’.
De hecho, siguiendo los pasos anteriores, simplemente creamos una tupla. Las tuplas en TypeScript son matrices de longitud fija cuyos tipos de valores son conocidos pero no se requiere que sean iguales.type Option = [string, boolean];
const option: Option = ['lowercase', true];
Funciones de palabras clave inferir
Ahora imaginemos que nuestro objetivo no es obtener lo que devuelve la función, sino solo obtener información sobre el tipo de datos que se le devuelve.type FunctionReturnType<FunctionType extends (...args: any) => ?> = ?;
El tipo anterior aún no está listo para usar. Necesitamos resolver el problema de cómo determinar el valor de retorno. Aquí puede describir todo manualmente, pero esto va en contra de nuestro objetivo.type FunctionReturnType<ReturnType, FunctionType extends (...args: any) => ReturnType> = ReturnType;
FunctionReturnType<number, typeof getRandomInteger>;
En lugar de hacer esto por nuestra cuenta, podemos pedirle a TypeScript que muestre el tipo de retorno. La palabra clave infer
solo se puede usar en tipos condicionales. Es por eso que nuestro código a veces puede ser algo desordenado.type FunctionReturnType<FunctionType extends (args: any) => any> = FunctionType extends (...args: any) => infer ReturnType ? ReturnType : any;
Esto es lo que sucede en este código:- Dice
FunctionType
expandirse aquí (args: any) => any
. - Señalamos que
FunctionReturnType
este es un tipo condicional. - Verificamos si se expande
FunctionType (...args: any) => infer ReturnType
.
Una vez hecho todo esto, podemos extraer el tipo de retorno de cualquier función.FunctionReturnType<typeof getRandomInteger>;
Lo anterior es una tarea tan común que TypeScript tiene una utilidad incorporada ReturnType , que está diseñada para resolver este problema.Construir como constante
Otro tema relacionado con la inferencia de tipos es la diferencia entre las palabras clave const
y las let
que se usan al declarar constantes y variables.let fruit = 'Banana';
const carrot = 'Carrot';
Variable fruit
: tiene un tipo string
. Esto significa que cualquier valor de cadena se puede almacenar en él.Y una constante carrot
es una cadena literal. Se puede considerar como un ejemplo de un subtipo string
. La siguiente descripción de literales de cadena se proporciona en este PR : "El literal de cadena de tipo es un tipo cuyo valor esperado es una cadena con contenido de texto equivalente al mismo contenido del literal de cadena".Este comportamiento puede ser cambiado. TypeScript 3.4 presenta una nueva característica interesante llamada aserciones constantes que proporciona el uso de una construcción as const
. Así es como se ve su uso:let fruit = 'Banana' as const;
Ahora fruit
es un literal de cadena. El diseño as const
también es conveniente cuando alguna entidad necesita ser inmutable. Considere el siguiente objeto:const user = {
name: 'John',
role: 'admin'
};
En JavaScript, la palabra clave const
significa que no puede sobrescribir lo que está almacenado en una constante user
. Pero, por otro lado, puede cambiar la estructura interna de un objeto registrado en esta constante.Ahora el objeto almacena los siguientes tipos:const user: {
name: string,
role: string
};
Para que el sistema perciba este objeto como inmutable, puede usar el diseño as const
:const user = {
name: 'John',
role: 'admin'
} as const;
Ahora los tipos han cambiado. Las cadenas se convirtieron en literales de cadena, no cadenas ordinarias. Pero no solo eso ha cambiado. Ahora las propiedades son de solo lectura:const user: {
readonly name: 'John',
readonly role: 'admin'
};
Y cuando se trabaja con matrices, se abren posibilidades aún más poderosas ante nosotros:const list = ['one', 'two', 3, 4];
El tipo de esta matriz es (string | number)[]
. Usando esta matriz, as const
puedes convertirla en una tupla:const list = ['one', 'two', 3, 4] as const;
Ahora el tipo de esta matriz se ve así:readonly ['one', 'two', 3, 4]
Todo esto se aplica a estructuras más complejas. Considere el ejemplo que Anders Halesberg dio en su discurso en TSConf 2019 :const colors = [
{ color: 'red', code: { rgb: [255, 0, 0], hex: '#FF0000' } },
{ color: 'green', code: { rgb: [0, 255, 0], hex: '#00FF00' } },
{ color: 'blue', code: { rgb: [0, 0, 255], hex: '#0000FF' } },
] as const;
Nuestra matriz colors
ahora está protegida contra cambios, y sus elementos también están protegidos contra cambios:const colors: readonly [
{
readonly color: 'red';
readonly code: {
readonly rgb: readonly [255, 0, 0];
readonly hex: '#FF0000';
};
},
]
Resumen
En este artículo, vimos algunos ejemplos del uso de mecanismos avanzados de inferencia de tipos en TypeScript. La palabra clave infer
y el mecanismo se utilizan aquí as const
. Estas herramientas pueden ser muy útiles en algunas situaciones particularmente difíciles. Por ejemplo, cuando necesita trabajar con entidades inmutables, o al escribir programas en un estilo funcional. Si desea continuar familiarizándose con este tema, eche un vistazo a este material.¡Queridos lectores! ¿Utiliza palabras clave infer
y construcción as const
en TypeScript?