Artículo por: Alberto Rivera Cristian Castillo
TypeScript | Genéricos y Casteo de tipos
¡Hola MiniCoder! En los últimos artículos de TypeScript aprendimos a tipar nuestro código por completo para darle mayor robustez y seguridad a las aplicaciones que creamos. Además, nos permitirá trabajar mejor al medio/largo plazo y autodocumentará nuestro código, ¿una maravilla no?
En estos nuevos artículos vamos a aprender a utilizar algunas características adicionales que pueden ser de utilidad y que seguramente encuentres en tu vida profesional con el tiempo, ¡vamos con ello!
¿Qué es una interface?
Es muy habitual en TypeScript encontrarnos en situaciones en las cuales no sabemos el tipo de dato con el que vamos a tener que trabajar, o en las que podemos recibir múltiples tipos de datos estructurados de una sola vez. En esos casos nos gustaría tipar pero sin tener que recurrir al any
o unknown
(que ya hemos comentado que no es lo más apropiado).
Es decir, si queremos definir un tipado más abstracto para hacer nuestro código robusto, pero ser capaces de especificarlo más adelante cuando tengamos conocimiento exacto del tipo que es, usaremos una herramienta clave de TypeScript, los Tipos Genéricos.
Funciones genéricas
Una función puede ser genérica cuando es independiente el tipo de dato que devuelva o reciba, porque la funcionalidad será la misma en cualquier caso:
const getFirst = (array) => array[0];
Si tipamos el return
como un valor number
, string
, boolean
.... estamos condicionando la utilidad de la función y esta sería muy limitada, por ello podemos usar los genéricos para tiparla. Estos se representan normalmente con una letra, generalmente T
.
const getFirst = <T,>(array: T[]): T => array[0]; // TypeScript dirá que es de tipo "string" const firstElement = getFirst(['Mini', 'Code', 'Lab']);
Interfaces genéricas
Podemos usar Tipos Genéricos en interfaces, esto es muy útil cuando desconocemos una propiedad o puede ser de naturaleza variada, como por ejemplo para tipar la respuesta en una Request a una API:
interface ReqResponse<T> { status: number; value: T; }
Con esto le estamos indicando que vamos a tener una interfaz ReqResponse
a la que pasamos un Tipo Genérico cualquiera, que coincidirá con el tipo de la propiedad value 🧙♂️. Aquí te dejamos un ejemplo de esto:
interface ReqResponse<T> { status: number; value: T; } const dataResponse: ReqResponse<string> = { status: 200, value: 'Mini Code Lab' }; const dataResponse: ReqResponse<boolean> = { status: 200, value: true };
También podemos establecer un valor por defecto en nuestra interfaz y omitir el tipo siempre y cuando coincida con el tipo por defecto.
interface ReqResponse<T = string> { status: number; value: T; } const dataResponse: ReqResponse = { status: 200, value: 'MiniCodeLab' };
Genéricos multiples
TypeScript nos permite definir varios genéricos y usarlos en la misma definición de tipo, ya que como hemos mencionado antes, usaremos letras para representarlos. Como te puedes imaginar, podemos usar tantos como queramos y no tenemos porque limitarnos a dos únicamente, aunque ten cuidado porque podemos crear funciones muy complejas de entender 🤯:
const compareType = <T, U>(arg1: T, arg2: U): boolean => typeof arg1 === typeof arg2;
Casteo de tipos o Type Assertion
En TypeScript debemos ser muy estrictos con los tipos que usamos para prevenir errores de código que sean difíciles de debugar. Para ello, utilizaremos types
e interfaces
de forma constante y así obtendremos el código robusto que nos permitirá llegar al siguiente nivel 🔥
A todo esto que hemos visto le acompaña un pequeño problema, ¿qué ocurre cuando el elemento o los datos que necesitamos vienen de una fuente externa?
Alguna vez habrás consumido una API con información inestable (algunos null
donde no debería, campos que pueden cambiar entre string
y number
...), o has buscado un elemento del DOM como un input
de tipo texto, un div
...
En TypeScript encontraremos esto un problema a la hora de tipar elementos externos, como en el siguente caso:
const inputElement = document.querySelector('input[type="text"]'); console.log(inputElement.value); // Nos dará el valor del input en este momento
Aunque parezca que este código funciona perfectamente, TypeScript nos informará del siguiente error:
Esto se debe a que el elemento puede (o no) existir en el DOM en momento de buscarlo.
Si realmente tienes la seguridad de que el elemento existe y esto solo es un falso positivo de TypeScript, puedes hacer lo que llamamos Type Assertion
con la palabra (o keyword) **as**
.
const inputElement = document.querySelector('input[type="text"]') as HTMLInputElement; console.log(inputElement.value); // Nos dará el valor del input en este momento
Ahora que hemos añadido as HTMLInputElement
a nuestro elemento, TypeScript pensará que es definitivamente un Input Element y no podrá ser nullable.
¡Recuerda! Esto es recomendable usarlo únicamente con tipos que se sobrecargan, datos que obtenemos de forma externa o elementos que puede no estar definidos en algún momento ❗
Enlaces de interés
Con esto ya estáis listos para subir el nivel de Typescript en vuestros proyectos. ¡Ahora os toca ponerlo en práctica a vosotros Mini Coders 🤓!