Original article: How to Use TypeScript in React Apps

¡Hola a todos! Hace un tiempo escribí un artículo sobre TypeScript (en inglés), explicando sus principales características y por qué es una buena idea usarlo en proyectos grandes.

Hoy vamos a echar un vistazo rápido a cómo podemos usar TypeScript en una aplicación React, para que puedas tener una idea de cómo se vería la implementación y cuáles son sus beneficios.

Tabla de contenido

Introducción a TypeScript

A estas alturas ya deberías saber que TypeScript es un superconjunto (superset) de JavaScript. Superset significa que agrega funciones además de lo que ofrece JavaScript.

TypeScript toma todas las funcionalidades y estructuras que proporciona JavaScript como lenguaje y le agrega algunas cosas. Lo principal que proporciona TypeScript es la escritura estática.

Cuando se trata de React, además de todo lo que podemos escribir en vanilla JS (como variables, parámetros de funciones y valores devueltos, etc.), podemos usar TypeScript principalmente para escribir dos cosas: props de componentes y hooks.

Una de las formas más sencillas de crear una aplicación React con TypeScript es usar CRA, ejecutando npx create-react-app my-app --template typescript.

Si ya tienes una aplicación CRA creada, en la documentación tienes información sobre cómo instalar TypeScript además de eso. ;)

Para los ejemplos aquí vamos a usar CRA, ya que es agradable y simple. Pero ten en cuenta que la mayoría de los marcos de trabajo (frameworks) como Next , Vite y Astro también brindan soporte para TypeScript.

Entonces, después de ejecutar el script de CRA, tendrás un proyecto que se parece a esto:

image-299

Como puedes ver, los archivos ahora tienen un nombre .tsx que es cómo el compilador de TypeScript identifica que usará TypeScript en ese archivo.

Y además, tenemos un tsconfig.json que es donde tenemos toda la configuración del compilador. Puedes aprender más sobre eso en el artículo anterior que escribí (en inglés).

Ahora vamos a crear un componente y ver cómo podemos usar TypeScript.

Tipos para Props

Para este ejemplo, configuraremos un componente ficticio responsable de representar un número recibido como props y agregarlo a ese número cuando se haga clic en un botón.

El código JavaScript normal se vería así:

const DummyComponent = ({ number, setNumber }) => {

  return (
    <>
      <div>{number}</div>

      <button
        onClick={() => setNumber(prev => prev+1)}
      >
        ADD
      </button>
    </>
  )

}

export default DummyComponent

Y nuestra versión completamente codificada se verá así:

import React, { Dispatch, SetStateAction } from 'react'

interface DummyProps {
  number: number
  setNumber: Dispatch<SetStateAction<number>>
}

const DummyComponent:React.FC<DummyProps> = ({ number, setNumber }) => {

  return (
    <>
      <div>{number}</div>

      <button
        onClick={() => setNumber(prev => prev+1)}
      >
        ADD
      </button>
    </>
  )

}

export default DummyComponent

Puedes ver que junto al nombre del componente, hemos agregado dos puntos y React.FC. Básicamente, esto le dice al componente TypeScript que DummyComponentes un componente funcional de React. Eso en sí no hace mucho, pero ayuda con el intellisense de TypeScript.

Junto a eso declaramos <DummyProps>. Esto declara que el objeto props que recibirá este componente debe coincidir con la interfaz DummyProps.

Una interfaz es la forma en que TypeScript le otorga un tipo a un objeto. Básicamente, declaramos todas las propiedades que tendrá ese objeto, y el tipo para cada una de ellas.

Dado que este componente recibirá un estado que es un número y una función para actualizar ese estado, eso es exactamente lo que tenemos en nuestra interfaz:

interface DummyProps {
  number: number
  setNumber: Dispatch<SetStateAction<number>>
}

Aquí puedes ver que para la función setNumber estamos usando este tipo: Dispatch<SetStateAction>. Este no es un tipo nativo de TypeScript, sino que lo proporciona React. Entonces tenemos que importarlo cada vez que lo usamos, así:
import React, { Dispatch, SetStateAction } from 'react'.

¡Y eso es todo! Les has dado tipos a tus props. Lo bueno de esto es que cada vez que llames a ese componente, obtendrás información sobre los props que espera ese componente. Lo mismo que si intentas pasar un prop no declarado en la interfaz del componente o proporcionas un tipo incorrecto para un prop esperado.

image-300
Intellisense en props esperados
image-301
Error de props inesperado
image-302
Tipo de prop incorrecto

Esto es lo que la gente quiere decir cuando dice que TypeScript autodocumenta su código. Con solo unas pocas líneas repetitivas, ahora puedes ver fácilmente qué espera y qué no espera cada componente. Esto es muy útil cuando se trabaja en proyectos grandes, con cientos de componentes que fueron escritos en su mayoría por otras personas. ;)

Tipos para hooks

Cuando se trata de hooks, TypeScript se usa principalmente para dar tipo a los hooks useState y useRef. Veamos cómo funciona eso.

Tipos para el hook UseState

Así es cómo se ve useState sin tipos:

const [number, setNumber] = useState<>(0)

Y con los tipos se ve así:

const [number, setNumber] = useState<number>(0)

Casi no hay necesidad de explicarlo, ¿verdad? Simplemente, declaramos el tipo del valor del estado de esta manera: <number>y eso es todo. Si alguna vez intentamos actualizar ese estado con un tipo de valor diferente, obtendremos un bonito mensaje de error rojo para evitar que nos disparemos en el pie. ;)

image-303
Error de tipo incorrecto

Ten en cuenta que si quieres permitir que nuestro estado tenga diferentes tipos de valores, podemos declararlo así: const [number, setNumber] = useState<number | string>(0).

Ahora podemos pasar un número O una cadena sin obtener errores.

Tipos para el hook UseRef

useRef es un hook que se utiliza principalmente para hacer referencia a elementos DOM en React. Si quieres obtener más información sobre cómo funciona el hook, puedes leer esta guía (en inglés) que escribí recientemente.

Para ver cómo podemos implementarlo con TypeScript, usaremos este ejemplo:

import React, { useEffect, useRef } from 'react'

const DummyComponent:React.FC = () => {

  const ref = useRef<HTMLInputElement>(null)

  useEffect(() => {
    if (ref.current) ref.current.focus()
  }, [])

  return (
      <input type="text" ref={ref} />
  )

}

export default DummyComponent

Como puedes ver, iniciamos la variable ref con null y declaramos su tipo como HTMLInputElement. Al usar el hook useRef y declarar su tipo, la variable se puede asignar a null o al tipo declarado.

Entonces tenemos un hook useEffect que enfoca el elemento si tiene una propiedad current. Y, por último, estamos devolviendo un elemento input y asignando la referencia que declaramos anteriormente: ref={ref}.

Lo bueno de escribir el hook useRef es que TypeScript evitará que intentemos realizar acciones o leer datos de tipos que no coinciden.

Por ejemplo, si declaramos el tipo number para la referencia, obtendríamos los siguientes errores:

image-364
La propiedad focus no existe en el tipo number
image-365
No se puede asignar una referencia number a un elemento HTML

Una vez más, esto es bueno porque evita errores tontos antes de tiempo y nos evita tener que depurar estas cosas más adelante. Especialmente cuando se trabaja con grandes bases de código en las que también trabajan muchas otras personas, TypeScript nos brinda un entorno más controlado y ordenado para trabajar.

Consideraciones finales

Bueno, como siempre, espero que hayáis disfrutado del artículo y aprendido algo nuevo.

Si queréis profundizar más en este tema, os recomiendo este vídeo (en inglés) de Firebase o este otro de Ben Awad.

Si queréis, también podéis seguirme en LinkedIn o Twitter. ¡Nos vemos en la próxima!

goodbye-farewell