Escriba inferencia con TypeScript utilizando la construcción as const y la palabra clave inferir

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 inferse 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 getRandomIntegerdevuelva 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 getRandomIntegerque 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 callpuede 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 infersolo 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 FunctionTypeexpandirse aquí (args: any) => any.
  • Señalamos que FunctionReturnTypeeste 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>; // number

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 consty las letque 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 carrotes 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 fruites un literal de cadena. El diseño as consttambién es conveniente cuando alguna entidad necesita ser inmutable. Considere el siguiente objeto:

const user = {
  name: 'John',
  role: 'admin'
};

En JavaScript, la palabra clave constsignifica 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 constpuedes 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 colorsahora 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 infery 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 infery construcción as consten TypeScript?


All Articles