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
Todos los frameworks React de grado de producción ofrecen soporte para el uso de TypeScript. Sigue la guía específica del framework para la instalación:
Añadir TypeScript a un proyecto React existente
Para instalar la última versión de las definiciones de tipos de React:
Las siguientes opciones del compilador deben ser configuradas en tu tsconfig.json
:
dom
debe incluirse enlib
(Nota: Si no se especifica la opciónlib
,dom
se incluye por defecto).jsx
debe configurarse con una de las opciones válidas.preserve
debería ser suficiente para la mayoría de las aplicaciones. Si vas a publicar una biblioteca, consulta la documentaciónjsx
para saber qué valor elegir.
TypeScript con Componentes de React
Escribir TypeScript con React es muy similar a escribir JavaScript con React. La diferencia clave cuando se trabaja con un componente es que puedes proporcionar tipos para las props de tu componente. Estos tipos se pueden usar para comprobar la corrección y proporcionar documentación en línea en los editores.
Tomando el componente MyButton
de la guía Inicio Rápido, podemos añadir un tipo que describa el title
para el 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 en línea es la forma más sencilla de proporcionar tipos para un componente, aunque una vez que empiezas a tener unos cuantos campos para describir puede llegar a ser inmanejable. En su lugar, puedes utilizar una interface
o type
para describir las props del componente:
interface MyButtonProps { /** El texto que se mostrará dentro del botón */ title: string; /** Si se puede interactuar con el botón */ 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 tan complejo como necesites, aunque debería ser un tipo de objeto descrito con type
o interface
. Puedes aprender sobre cómo TypeScript describe objetos en Object Types pero también puedes estar interesado en usar Union Types para describir una prop que puede ser uno de varios tipos diferentes y la guía Creating Types from Types para casos de uso más avanzados.
Ejemplos de Hooks
Las definiciones de tipos de @types/react
incluyen tipos para los Hooks incorporados, por lo que puedes usarlos en tus componentes sin ninguna configuración adicional. Están construidos para tener en cuenta el código que escribes en tu componente, por lo que obtendrás tipos inferidos la mayor parte del tiempo e idealmente no necesitarás manejar las minucias de proporcionar los tipos.
Sin embargo, podemos ver algunos ejemplos de cómo proporcionar tipos para Hooks.
useState
El Hook useState
reutilizará el valor pasado como estado inicial para determinar cuál debe ser el tipo del valor. Por ejemplo:
// Infiere el tipo como "boolean"
const [enabled, setEnabled] = useState(false);
Asignará el tipo boolean
a enabled
, y setEnabled
será una función que acepte un argumento boolean
, o una función que devuelva un boolean
. Si quieres proporcionar explícitamente un tipo para el estado, puedes hacerlo proporcionando un argumento de tipo a la llamada useState
:
// Definiendo explícitamente el tipo como "boolean"
const [enabled, setEnabled] = useState<boolean>(false);
Esto no es muy útil en este caso, pero un caso común en el que es posible que desees proporcionar un tipo es cuando tienes un tipo de unión. Por ejemplo, status
puede 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 hacia abajo en el árbol de componentes sin tener que pasar props a través de los componentes. Se utiliza creando un componente proveedor y, a menudo, creando un Hook para consumir 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 tiene sentido - pero hay casos ocasionales en los que no, y en esos casos null
puede parecer razonable como valor por defecto. Sin embargo, para permitir que el sistema de tipos entienda tu código, necesitas establecer explícitamente ContextShape | null
en el createContext
.
Esto causa el problema de que necesitas eliminar el | null
en el tipo para los consumidores de contexto. Nuestra recomendación es que el Hook compruebe su existencia en tiempo de ejecución y lance un error si 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
Los hooks useMemo
crearán/reaccederán a un valor memorizado desde una llamada a una función, reejecutando la función sólo cuando las dependencias pasadas como segundo parámetro cambien. El resultado de llamar al Hook se infiere del valor de devolución de la función en el primer parámetro. Se puede ser más explícito proporcionando 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.
Dependiendo de tus preferencias de estilo de código, podrías usar 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
Hay un conjunto bastante amplio de tipos que provienen del paquete @types/react
, vale la pena leerlo cuando te sientas cómodo con cómo interactúan React y TypeScript. Puedes encontrarlos en la carpeta de React en DefinitelyTyped. Cubriremos algunos de los tipos más comunes aquí.
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 caminos comunes para describir los hijos de un componente. El primero es utilizar el tipo React.ReactNode
, que es una unión de todos los tipos posibles que se pueden pasar como hijos en JSX:
interface ModalRendererProps {
title: string;
children: React.ReactNode;
}
Esta es una definición muy amplia de hijos. El segundo es utilizar el tipo React.ReactElement
, que es sólo elementos JSX y no primitivas JavaScript como strings o numbers:
interface ModalRendererProps {
title: string;
children: React.ReactElement;
}
Ten en cuenta que no puedes usar TypeScript para describir que los hijos son un cierto tipo de elementos JSX, por lo que no puedes usar el sistema de tipos para describir un componente que sólo acepta hijos <li>
.
Puedes ver un ejemplo tanto de React.ReactNode
como de React.ReactElement
con el verificador de tipos en este playground de TypeScript.
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 las nuevas características 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 un lugar excelente para hacer preguntas y obtener ayuda con problemas de TypeScript y React.