Usar TypeScript
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.
Aprenderás
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:
En tu archivo tsconfig.json
, debes configurar las siguientes opciones del compilador:
- Incluye
dom
enlib
(Nota: Si no se especifica ninguna opciónlib
,dom
se incluye de manera predeterminada). - 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 dejsx
para elegir el valor adecuado.
TypeScript con Componentes de React
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> ); }
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 queuseReducer
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.