7 CONSEJOS para ser UN MAGO de TypeScript 🧙‍♂️

⚠️ Es fácil añadir TypeScript a tu proyecto JavaScript y presumir de que estás utilizando tipos, pero no lo es tanto garantizar un tipado estricto en todo el código. Estos son 7 CONSEJOS para utilizar TypeScript correctamente.

Categorías
Desarrollo web

Antes de los consejos de TypeScript

Antes de pasar a los consejos de TypeScript, vamos a dar un poco de contexto del lenguaje.

JavaScript es el lenguaje de programación más demandado en el mercado y uno de los más queridos por la comunidad de desarrolladores en todo el mundo.

Logo animado de JavaScript.

Se trata de un lenguaje muy flexible y fácil de aprender, entre otras cosas, gracias a su tipado dinámico, que se encarga de asignar por nosotros un tipo u otro a las variables dependiendo del valor que tengan en cada momento de la ejecución.

El precio de desarrollar un software sin tipado es que todos los errores de incompatibilidad que podríamos detectar mientras escribimos el código se permiten y dan paso a errores en tiempo de ejecución que solo descubrirás una vez se hayan producido. Así que la mayoría del tiempo que ahorres gracias al tipado dinámico la acabarás utilizando en encontrar los bugs que van apareciendo y solucionarlos.

¿Y cómo podríamos aprovechar las ventajas de JavaScript y evitar su propensión a errores durante la ejecución?

Pues utilizando TypeScript. Una capa de tipado para JavaScript desarrollada por Microsoft que se encarga de capturar los errores de incompatibilidad en variables mientras escribimos el código en lugar de tener que esperar a que alguien encuentre el fallo, lo reporte y lo solucionemos.

Soy Carlos Sala, desarrollador de software y en este artículo no voy a explicar TypeScript desde cero, porque para eso está la documentación oficial de Microsoft. Pero sí voy a darte 7 CONSEJOS para utilizar TypeScript correctamente y aprovechar todas sus posibilidades.

YouTube player

#1 – Utiliza TypeScript ESLint

¿Alguna vez has utilizado ESLint en un proyecto de JavaScript?

ESLint es una herramienta que podemos instalar a través del gestor de dependencias NPM o Yarn y que analizará el código de todos los archivos para encontrar potenciales problemas durante la ejecución y para ayudar a mantener un estilo de código consistente en todo el proyecto. Para mí es una herramienta fundamental para optimizar la legibilidad y el mantenimiento del código.

El plugin TypeScript ESLint extiende las funcionalidades de ESLint para analizar la sintaxis de TypeScript y obligarnos a seguir las buenas prácticas del lenguaje cuando escribimos el código y formatearlo adecuadamente.

Las reglas TypeScript ESLint son totalmente personalizables, por lo que podremos arrancar con la configuración recomendada e ir modificándola para adaptarla a nuestro proyecto y preferencias personales. Por poner algunos ejemplos de reglas disponibles, encontramos:

  • No permitir la declaración de interfaces vacías
  • No permitir el uso del tipo any de forma explícita
  • O no especificar explícitamente el tipo de datos simple que puede ser inferido de una variable con asignación.

Para poner el broche al uso de TypeScript ESLint, lo que yo hago siempre para forzarme a seguir estas reglas es instalar la extensión ESLint en Visual Studio Code o el IDE que esté utilizando. Así recibo en tiempo real el análisis de ESLint sin tener que ejecutar ningún comando en la terminal.

#2 – Configuración estricta de TypeScript

Para asegurarnos de que TypeScript cumple su función de capturar todos los errores de tipado durante el desarrollo, debemos configurarlo adecuadamente en nuestro proyecto.

En el archivo tsconfig.json, dentro del apartado compilarOptions deberemos marcar como true la opción strict para activar el tipado estricto.

tsconfig.json
{
  "compilerOptions": {
    "strict": true
  }
}

Este flag activa de forma global una serie de opciones relacionadas con la comprobación de tipos. Algunas de ellas son:

  • noImplicitAny: Que nos fuerza a añadir tipos a todas las declaraciones de variables de forma explícita.
  • strictNullChecks: Con la que debemos comprobar si una variable es distinta de ?null? o ?undefined? antes de utilizarla. Como en este ejemplo en el que, tras usar el método ?find? de un ?array? debemos comprobar si efectivamente se ha encontrado algún resultado o de lo contrario obtendremos el error ?object is possibly undefined?.
  • strictFunctionTypes: Que comprobará estrictamente que los tipos de los parámetros de una función son los correctos.

Como he dicho al principio, si estamos utilizando TypeScript es para evitar sustos durante la ejecución de nuestra aplicación, no para presumir de que lo estamos utilizando.

Entonces de nada sirve tener una configuración más flexible de TypeScript para programar sin llevar cuidado si luego todos esos errores que hemos introducido van a ir apareciendo.

#3 – Utiliza los Generics de TypeScript

¿Utilizas los Generics de TypeScript?

Al igual que los Generics de Java o los Templates de C++, en TypeScript podemos escribir código que no está atado a ningún tipo determinado, si no a un tipo que le pasamos como parámetro. Esto nos permite crear código reutilizable y aplicarlo a diferentes tipos de datos.

El pokemon Ditto posando para ilustrar los generics en TypeScript.

Podemos utilizar Generics en tipos, funciones o clases para evitar duplicar código que, a excepción de uno o más tipos, tiene el mismo funcionamiento. Vamos a verlo con un ejemplo muy sencillo para entenderlo mejor.

Imagina que queremos implementar la estructura de datos de una pila. Para empezar escribimos una interfaz Stack, que es pila en inglés, que define los métodos que queremos exponer públicamente de esta estructura de datos, que serían:

  • El método push que nos permite añadir un elemento sobre el último elemento que ha sido añadido.
  • El método pop que se encarga de expulsar el último elemento que se ha añadido a la pila, el que se encuentra arriba del todo.
  • Un método peek que devuelve el último elemento añadido, de nuevo el de más arriba.
  • Y un método size que devuelve un valor numérico con el número de elementos que tiene la pila.
Stack.ts
interface Stack<ItemType> {
  push: (item: ItemType) => void;
  pop: () => ItemType | undefined;
  peek: () => ItemType | undefined;
  size: () => number;
}

Como véis, esta interfaz recibe un tipo como parámetro llamado ItemType que nos va a permitir reutilizarla independientemente del tipo de datos que queramos apilar.

La forma de utilizar esta interfaz genérica sería especificando el tipo de elemento que vamos a utilizar.

#4 – Aprovecha la inferencia de tipos en TypeScript

Aunque es importante que el 100% de nuestro código tenga un tipo asignado para tener las garantías de que funciona correctamente, no tenemos por qué añadir anotaciones a todas las variables o valores de retorno de funciones explícitamente.

La inferencia de tipos en TypeScript es una característica que se encarga de calcular por nosotros el tipo que corresponde a una variable siempre que tenga la información suficiente para deducirlo.

La forma más sencilla de ilustrarlo es a través de un Array. En la siguiente asignación a la variable x, TypeScript es capaz de inferir que el tipo que corresponde a esa variable es un Array que acepta elementos tanto del tipo number como del tipo null. En este caso sería redundante añadir además la anotación con ese mismo tipo a la variable.

const x = [1, null];

Otro escenario común se da con los tipos de retorno de funciones. En la siguiente función, dado que los parámetros son ambos tipos numéricos y el valor que devuelve esta función es la suma de los 2 parámetros, se tiene la información suficiente para garantizar que el tipo de retorno que tiene esta función va a ser siempre un número. Así que tampoco tendríamos por qué añadir esa anotación.

function sum(a: number, b: number) {
  return a + b;
}

Con cada nueva versión de TypeScript la inferencia de tipos es más potente y cubre escenarios más complejos. Esto nos permite a los desarrolladores escribir menos código sin perder garantías de que vamos a recibir los tipos esperados.

En este punto hay gente que prefiere especificar explícitamente todos los tipos y es algo que puedes configurar de forma consistente utilizando TypeScript Eslint.

#5 – Utiliza librerías con soporte para TypeScript

Aunque la mayoría de librerías de JavaScript hoy en día incluyen su declaración de tipos en TypeScript para hacerlas más seguras, es nuestra responsabilidad garantizar como desarrolladores que todo el código de nuestra aplicación está fuertemente tipado.

Ya que si hemos añadido TypeScript a nuestro proyecto es justamente para disminuir los errores que tienen origen en el tipo de las variables dinámicas de JavaScript.

A la hora de escoger una librería para nuestro proyecto deberemos valorar si cumple una de las siguientes condiciones:

  • Si incluye la definición de tipos en su paquete oficial
  • Si en su defecto circula por Internet un paquete actualizado que define la API de la librería en un repositorio como DefinitelyTyped, que se encarga justamente de añadir la capa de tipos a los paquetes de JavaScript que oficialmente no la incluyen.
  • O si, en el peor de los casos, nos va a tocar a nosotros definir los tipos de la librería en un archivo de declaración de TypeScript consultando el código fuente de la librería.
prettysize.d.ts
declare module 'prettysize' {
  export default function (bytes: number | string, removeSpace?: boolean): string;
}

Dependiendo del caso escogeremos una u otra de las 3 opciones, pero es importante no hacer bypass del código de terceros porque a fin de cuentas será nuestra aplicación la que acabe fallando.

#6 – Evita el Type Casting

Si tu intención es utilizar TypeScript incorrectamente y dejar la puerta abierta a errores y comportamientos inesperados de tu código, el Type Casting es la forma más rápida de conseguirlo.

El Type Casting es la funcionalidad de TypeScript que nos permite asignar forzosamente un tipo a una variable de la que no tenemos la certeza del tipo de valor que tiene. Cuando programamos en TypeScript es relativamente sencillo llegar a una situación en la que recibimos un valor del tipo any o unknown porque en algún punto de la traza por la que ha viajado ese valor el tipo no ha sido especificado.

Comúnmente ocurre cuando olvidamos pasar un tipo como parámetro a un Generic que tiene any o unknown como valores por defecto o utilizamos una librería que no ha sido tipada de forma estricta.

const value: unknown = 1;
const a: string = value as string;
const value: unknown = 1;
const a: string = <string>value;

Podemos castear variables utilizando la palabra clave as seguida del tipo al que queremos convertir o especificar el tipo entre los operadores ?menor que? y ?mayor que?. Pero la forma correcta de tratar estos casos es encontrar el punto en el que se ha perdido el tipado siguiendo la traza y solucionarlo; o si el problema proviene de código de terceros, construir un wrapper que añada las anotaciones necesarias para garantizar el tipado seguro sin tener que hacer suposiciones y sin asumir los riesgos que esto conlleva.

Soy programador y entiendo que las prisas de terminar un proyecto a tiempo y demás condiciones pueden incitarte a castear variables para que el compilador de TypeScript no devuelva ningún error de incompatibilidad. Pero creo que son estos momentos en los que se ve la diferencia entre un desarrollador chapucero y los que quieren hacer bien su trabajo. 

#7 – Utiliza los Type Aliases de TypeScript

¿Estás aprovechando todas las ventajas de los Type Aliases? En TypeScript existen 2 formas de definir la forma de un objeto:

  • Definiendo interfaces que podemos extender utilizando la palabra clave extends de manera muy similar a las clases en JavaScript.
  • Y mediante Type Aliases, utilizando la palabra clave type seguida de un identificador a la que asignaremos el tipo que estemos definiendo. La forma de extender estos Type Aliases es utilizando operadores para combinarlos formando intersecciones o uniones.
Extensión de interfaz
interface Indexable {
  id: number;
}

interface Book extends Indexable {
  title: string;
}
Extensión de tipo
interface Indexable {
  id: number;
}

type Book = Indexable & {
  title: string;
}

Es cierto que en los casos más sencillos es indiferente utilizar un alias o una interfaz para definir tipos. Pero en cuanto necesitamos formar tipos complejos haciendo combinaciones con otros tipos definidos es cuando se hace imprescindible el uso de los Type Aliases para garantizar principalmente las 2 siguientes características en nuestro código:

  • Se mejora la legibilidad del proyecto al dar nombres significativos a tipos complejos para hacer el código más fácil de entender y mantener limpias al resto de definiciones en las que participa este tipo.
  • Evitamos duplicar la lógica de definición de tipos. Se reutiliza la composición de estos tipos derivados de otros exportando los Type Aliases y utilizándolos donde nos hagan falta.
interface File {
  id: number;
  name: string;
}

interface Folder {
  id: number;
  name: string;
  color: string;
}

export type DriveItem = File | Folder;

Si estás teniendo dificultades definiendo tipos y abusas de las interfaces porque no conocías o no sueles usar los Type Aliases en tu código, échale un vistazo a la documentación oficial, porque probablemente esta es una de las piezas que te falta para hacer más agradable el desarrollo en TypeScript y para escalar tu proyecto de una forma sostenible.

Conclusión

Estos son solo 7 de las muchas buenas prácticas que podríamos enumerar para mejorar tu código en TypeScript.

Niña diciendo adiós para despedir este artículo de consejos de TypeScript.

Añadir tipos a nuestro código JavaScript es una mejora de calidad que se encarga de filtrar una gran parte de los errores de ejecución. Pero debemos hacerlo correctamente para no crear falsos positivos, como en el caso de los Type Castings, que aunque pasan la validación de TypeScript, introducen comportamientos inesperados y errores en nuestro código.

Además también hemos visto que aunque es fácil configurar TypeScript en tu proyecto, algunas definiciones de tipos se complican hasta el infinito y la práctica, el ensayo y error o consultar el código de otras personas son los mejores maestros para dominar todas las características de TypeScript y adquirir el ?mindset? que requiere la definición de tipos.

Si te ha sido útil este contenido, puedes apoyarme suscribiéndote a mi canal de YouTube y siguiéndome en redes sociales.

Carlos Sala Samper

Handmade software.