TypeScript es una forma popular de añadir definiciones de tipos a bases de código JavaScript. De manera predeterminada, TypeScript soporta JSX y puedes obtener soporte completo para React Web añadiendo @types/react y @types/react-dom a tu proyecto.

Instalación

Cada framework de React orientado a producción brinda compatibilidad para el uso de TypeScript. Consulta la guía específica de tu framework para su instalación:

Agregar TypeScript a un proyecto de React ya existente

Para instalar las definiciones de tipos más recientes de React:

Terminal
npm install @types/react @types/react-dom

En tu archivo tsconfig.json, debes configurar las siguientes opciones del compilador:

  1. Incluye dom en lib (Nota: Si no se especifica ninguna opción lib, dom se incluye de manera predeterminada).
  2. Establece jsx en una de las opciones válidas. Para la mayoría de aplicaciones, preserve debería ser suficiente. Si estás publicando una biblioteca, consulta la documentación de jsx para elegir el valor adecuado.

TypeScript con Componentes de React

Nota

Cada archivo que contenga JSX debe usar la extensión de archivo .tsx. Esta es una extensión específica de TypeScript que indica a TypeScript que este archivo contiene JSX.

Escribir TypeScript con React se asemeja mucho a escribir JavaScript con React. La diferencia principal radica en que al trabajar con un componente puedes definir tipos para sus props. Esos tipos se utilizan para realizar verificaciones de corrección y brindar documentación inline en los editores.

Si tomamos el componente MyButton de la guía de Inicio Rápido, podemos agregar un tipo que describa el title del botón:

function MyButton({ title }: { title: string }) {
  return (
    <button>{title}</button>
  );
}

export default function MyApp() {
  return (
    <div>
      <h1>Bienvenido a mi aplicación</h1>
      <MyButton title="Soy un botón" />
    </div>
  );
}

Nota

Si bien estos entornos de prueba pueden manejar código TypeScript, no ejecutan la verificación de tipos. Esto significa que puedes ajustar los entornos de prueba de TypeScript para aprender, pero no recibirás errores o advertencias de tipo. Si deseas la verificación de tipos, puedes utilizar el TypeScript Playground o recurrir a un entorno de pruebas en línea más completo.

Esta sintaxis inline es la forma más sencilla de definir tipos para un componente, aunque a medida que empieces a tener varios campos que describir, puede volverse incómodo. En cambio, puedes usar una interface o type para describir las props del componente:

interface MyButtonProps {
  /** El texto que se mostrará dentro del botón */
  title: string;
  /** Si el botón es interactivo */
  disabled: boolean;
}

function MyButton({ title, disabled }: MyButtonProps) {
  return (
    <button disabled={disabled}>{title}</button>
  );
}

export default function MyApp() {
  return (
    <div>
      <h1>Bienvenido a mi aplicación</h1>
      <MyButton title="Soy un botón desactivado" disabled={true}/>
    </div>
  );
}

El tipo que describe las props de tu componente puede ser tan simple o complejo como requieras, aunque debería ser un tipo de objeto descrito mediante una instrucción type o interface. Puedes aprender sobre cómo TypeScript describe objectos en Object Types pero también podría interesarte el uso de Union Types para describir una prop que puede ser de varios tipos, y consultar la guía de Creating Types from Types para casos de uso más avanzados.

Ejemplos de Hooks

Las definiciones de tipos proporcionados por @types/react incluyen tipos para hooks nativos, lo que te permite usarlos en tus componentes sin necesidad de configuración adicional. Estos tipos están diseñados para tener en cuenta el código que escribes en tu componente, por lo que la mayoría de las veces obtendrás tipos inferidos y, en teoría, no necesitarás ocuparte de proporcionar tipos.

No obstante, podemos explorar algunos ejemplos de cómo proporcionar tipos para los hooks.

useState

El hook useState reutilizará el valor proporcionado como estado inicial para determinar qué tipo debe tener el valor. Por ejemplo:

// Infiere el tipo como "boolean"
const [enabled, setEnabled] = useState(false);

Asignará el tipo boolean a enabled, y setEnabled será una function que aceptará un argumento boolean o una función que devolverá un valor boolean. Si deseas proporcionar explícitamente un tipo para el estado, puedes hacerlo proporcionando un argumento de tipo en la llamada a useState:

// Definiendo explícitamente el tipo como "boolean"
const [enabled, setEnabled] = useState<boolean>(false);

Aunque no es muy útil en este caso, una situación común donde querrías proporcionar un tipo es cuando tienes un tipo de unión. Por ejemplo, aquí status podría ser uno de varios strings diferentes:

type Status = "idle" | "loading" | "success" | "error";

const [status, setStatus] = useState<Status>("idle");

O bien, como se recomienda en Principios para la estructuración del estado, puedes agrupar estados relacionados en un objeto y describir las diferentes posibilidades a través de objetos de tipo:

type RequestState =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success', data: any }
| { status: 'error', error: Error };

const [requestState, setRequestState] = useState<RequestState>({ status: 'idle' });

useReducer

El hook useReducer es un hook más complejo que recibe una función reductora y un estado inicial. Los tipos para la función reductora se infieren a partir del estado inicial. Opcionalmente, puedes proporcionar un argumento de tipo a la llamada del useReducer para dar un tipo al estado, pero generalmente es mejor establecer el tipo en el estado inicial:

import {useReducer} from 'react';

interface State {
   count: number 
};

type CounterAction =
  | { type: "reset" }
  | { type: "setCount"; value: State["count"] }

const initialState: State = { count: 0 };

function stateReducer(state: State, action: CounterAction): State {
  switch (action.type) {
    case "reset":
      return initialState;
    case "setCount":
      return { ...state, count: action.value };
    default:
      throw new Error("Unknown action");
  }
}

export default function App() {
  const [state, dispatch] = useReducer(stateReducer, initialState);

  const addFive = () => dispatch({ type: "setCount", value: state.count + 5 });
  const reset = () => dispatch({ type: "reset" });

  return (
    <div>
      <h1>Bienvenido a mi contador</h1>

      <p>Contador: {state.count}</p>
      <button onClick={addFive}>Sumar 5</button>
      <button onClick={reset}>Reiniciar</button>
    </div>
  );
}

Usamos TypeScript en algunos lugares clave:

  • interface State describe la estructura del estado del reductor.
  • type CounterAction describe las diferentes acciones que puedes ser despachadas al reductor.
  • const initialState: State proporciona un tipo para el estado inicial, y también el tipo que useReducer utiliza por defecto.
  • stateReducer(state: State, action: CounterAction): State define los tipos para los argumentos y el valor de devolución de la función reductora.

Una alternativa más explícita para definir el tipo en initialState es proporcionar un argumento de tipo a useReducer:

import { stateReducer, State } from './your-reducer-implementation';

const initialState = { count: 0 };

export default function App() {
const [state, dispatch] = useReducer<State>(stateReducer, initialState);
}

useContext

El hook useContext es una técnica para pasar datos por el árbol de componentes sin tener que pasar props a través de ellos. Se utiliza creando un componente provider y, a menudo, creando un hook que consuma el valor en un componente hijo.

El tipo del valor proporcionado por el contexto se infiere a partir del valor pasado a la llamada de createContext:

import { createContext, useContext, useState } from 'react';

type Theme = "light" | "dark" | "system";
const ThemeContext = createContext<Theme>("system");

const useGetTheme = () => useContext(ThemeContext);

export default function MyApp() {
  const [theme, setTheme] = useState<Theme>('light');

  return (
    <ThemeContext.Provider value={theme}>
      <MyComponent />
    </ThemeContext.Provider>
  )
}

function MyComponent() {
  const theme = useGetTheme();

  return (
    <div>
      <p>Aspecto actual: {theme}</p>
    </div>
  )
}

Esta técnica funciona cuando tienes un valor por defecto que hace sentido - pero ocasionalmente hay casos en los que no lo tienes, y en esos casos, null puede ser un valor por defecto razonable. Sin embargo, para permitir el sistema de tipos comprenda tu código, tienes que establecer de manera explícita ContextShape | null en la llamada a createContext.

Esto genera el problema de que debes eliminar el | null en el tipo para los consumidores del context. Nuestra recomendación es que el hook realice una comprobación en tiempo de ejecución para asegurarse de su existencia y lance un error cuando no esté presente:

import { createContext, useContext, useState, useMemo } from 'react';

// Este ejemplo es más sencillo, pero puedes imaginar un objeto más complejo aquí
type ComplexObject = {
kind: string
};

// El context se crea con `| null` en el tipo, para reflejar con exactitud el valor predeterminado.
const Context = createContext<ComplexObject | null>(null);

// El `| null` será eliminado mediante la verificación en el hook.
const useGetComplexObject = () => {
const object = useContext(Context);
if (!object) { throw new Error("useGetComplexObject must be used within a Provider") }
return object;
}

export default function MyApp() {
const object = useMemo(() => ({ kind: "complex" }), []);

return (
<Context.Provider value={object}>
<MyComponent />
</Context.Provider>
)
}

function MyComponent() {
const object = useGetComplexObject();

return (
<div>
<p>Objeto actual: {object.kind}</p>
</div>
)
}

useMemo

El hook useMemo creará o reaccederá a un valor memorizado a partir de una llamada a una función, volviendo a ejecutar la función sólo cuando cambien las dependencias pasadas como segundo parámetro. El resultado de llamar al hook se infiere a partir del valor de devolución de la función en el primer parámetro. Puedes ser más explícito al proporcionar un argumento de tipo al hook.

// El tipo de visibleTodos se infiere del valor de devolución de filterTodos
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);

useCallback

El useCallback proporciona una referencia estable a una funcion siempre y cuando las dependencias pasadas como segundo parámetro sean las mismas. Al igual que con useMemo, el tipo de la función se infiere del valor de devolución de la función en el primer parámetro, y puedes ser más explícito al proporcionar un argumento de tipo al hook.

const handleClick = useCallback(() => {
// ...
}, [todos]);

Cuando trabajas en el modo estricto de TypeScript, useCallback necesita que agregues tipos para los parámetros en tu función callback. Esto se debe a que el tipo del callback se infiere a partir del valor de devolución de la función, y sin parámetros no se puede entender completamente el tipo.

Según tus preferencias de estilo de código, podrías utilizar las funciones *EventHandler de los tipos de React para proporcionar el tipo para el controlador de eventos al mismo tiempo que defines el callback:

import { useState, useCallback } from 'react';

export default function Form() {
const [value, setValue] = useState("Change me");

const handleChange = useCallback<React.ChangeEventHandler<HTMLInputElement>>((event) => {
setValue(event.currentTarget.value);
}, [setValue])

return (
<>
<input value={value} onChange={handleChange} />
<p>Value: {value}</p>
</>
);
}

Tipos útiles

Dentro del paquete @types/react se encuentra un conjunto bastante amplio de tipos. Te sugerimos revisarlos cuando te sientas cómodo con la interacción entre React y TypeScript. Puedes encontrarlos en la carpeta de React en DefinitelyTyped. A continuación, vamos a repasar algunos de los tipos más frecuentes.

Eventos del DOM

Cuando trabajas con eventos del DOM en React, el tipo del evento suele inferirse a partir del controlador de eventos. Sin embargo, cuando desear extraer una función para ser pasada a un controlador de eventos, necesitarás establecer de manera explícita el tipo del evento.

import { useState } from 'react';

export default function Form() {
  const [value, setValue] = useState("Cámbiame");

  function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
    setValue(event.currentTarget.value);
  }

  return (
    <>
      <input value={value} onChange={handleChange} />
      <p>Valor: {value}</p>
    </>
  );
}

Existen muchos tipos de eventos disponibles en los tipos de React - la lista completa se puede encontrar aquí, la cual se basa en los eventos más populares del DOM.

Al determinar el tipo que estás buscando, puedes mirar primero la información emergente para el controlador de eventos que estás utilizando, lo cual mostrará el tipo del evento.

Si necesitas utilizar un evento que no está incluido en esta lista, puedes emplear el tipo React.SyntheticEvent, que es el tipo base para todos los eventos.

Elementos hijos

Hay dos enfoques comunes para describir los elementos hijos de un componente. El primero es usar el tipo React.ReactNode, que es una unión de todos los tipos posibles que se pueden pasar como elementos hijos en JSX:

interface ModalRendererProps {
title: string;
children: React.ReactNode;
}

Esta es una definición bastante amplia de children. La segunda opción es emplear el tipo React.ReactElement, que incluye solamente elementos JSX y no incluye primitivos de JavaScript como strings o números:

interface ModalRendererProps {
title: string;
children: React.ReactElement;
}

Ten en cuenta que no puedes utilizar TypeScript para describir que los elementos hijos sean de un cierto tipo de elementos JSX, por lo que no puedes usar el sistema de tipos para describir un componente que solo acepta elementos <li> como hijos.

Puedes ver un ejemplo tanto de React.ReactNode como de React.ReactElement con el verificador de tipos en este TypeScript playground.

Props de estilo

Cuando utilizas estilos en linea en React, puedes emplear React.CSSProperties para describir el objeto que se pasa a la prop style. Este tipo es una unión de todas las posibles propiedades CSS y es una forma efectiva de garantizar que estás proporcionando propiedades CSS válidas a la prop style, además de obtener autocompletado en tu editor.

interface MyComponentProps {
style: React.CSSProperties;
}

Recursos adicionales

Esta guía ha abordado los fundamentos para usar TypeScript con React, pero hay mucho más por aprender. Las páginas individuales de la API en la documentación pueden contener információn más detallada sobre cómo utilizarlas con TypeScript.

Recomendamos los siguientes recursos:

  • The TypeScript handbook es la documentación oficial para TypeScript, y abarca la mayoría de las características clave del lenguaje.

  • The TypeScript release notes cubre cada una de las nuevas característica en profundidad.

  • React TypeScript Cheatsheet es una hoja de referencia mantenida por la comunidad que trata sobre cómo utilizar TypeScript con React, abordando muchos casos útiles y proporcionando un enfoque más amplio que este documento.

  • TypeScript Community Discord es excelente lugar para hacer preguntas y obtener ayuda con problemas de TypeScript y React.