Por qué TypeScript en React
En proyectos profesionales, React casi siempre se escribe con TypeScript. La
ventaja es enorme: el editor te avisa de errores antes de ejecutar (una prop
mal escrita, un valor que puede ser undefined, un handler con el tipo
equivocado), y el código se autodocumenta. Para React, lo más importante es
tipar las props, el estado y los eventos.
Nota: TypeScript se compila a JavaScript; en el navegador siempre acaba ejecutándose JS. Aquí veremos la teoría con ejemplos
.tsx; los ejercicios prácticos de este módulo se escriben en JS válido.
Tipar las props con interface o type
Las props de un componente son un objeto, así que describimos su forma con una
interface (o un type). Luego la usamos para tipar el parámetro:
interface SaludoProps {
nombre: string;
edad?: number; // el "?" la hace opcional
}
function Saludo({ nombre, edad }: SaludoProps) {
return <h1>Hola, {nombre}{edad ? ` (${edad})` : ""}</h1>;
}
nombre: string→ obligatoria. Si la olvidas al usar<Saludo />, error en compilación.edad?: number→ opcional. Dentro del componente,edadesnumber | undefined.
interface y type son casi intercambiables para props; usa el que prefiera tu
equipo. type permite además uniones (type Estado = "ok" | "error").
Componentes: función tipada o React.FC
La forma recomendada hoy es una función normal con las props tipadas (como
arriba). Existe también React.FC, que tipa el componente entero e incluye
children de forma implícita en versiones antiguas:
const Boton: React.FC<{ texto: string }> = ({ texto }) => {
return <button>{texto}</button>;
};
En React moderno se prefiere la función tipada frente a
React.FC, porque es más explícita (declaraschildrensolo si lo necesitas) y más flexible.
Estado tipado: useState<T>
useState infiere el tipo a partir del valor inicial:
const [nombre, setNombre] = useState(""); // string
const [activo, setActivo] = useState(false); // boolean
Cuando el valor inicial no basta para inferir (p. ej. empieza en null pero luego
guardará un objeto), se anota explícitamente con useState<T>:
interface Usuario { id: number; nombre: string; }
const [usuario, setUsuario] = useState<Usuario | null>(null);
Tipar eventos
Los manejadores de eventos reciben un evento de React, no del DOM nativo. Los
tipos viven en el espacio React.*:
function Campo() {
const [texto, setTexto] = useState("");
function alEscribir(e: React.ChangeEvent<HTMLInputElement>) {
setTexto(e.target.value);
}
function alEnviar(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
}
return (
<form onSubmit={alEnviar}>
<input value={texto} onChange={alEscribir} />
</form>
);
}
Los más habituales: React.ChangeEvent<HTMLInputElement> (inputs),
React.MouseEvent<HTMLButtonElement> (clics), React.FormEvent (formularios) y
React.KeyboardEvent (teclado). Si escribes el handler en línea
(onClick={(e) => ...}), TypeScript suele inferir el tipo del evento por ti.
children y React.ReactNode
children es "lo que va entre las etiquetas de apertura y cierre" del componente.
Como puede ser texto, un número, un elemento JSX, una lista de ellos o nada, su
tipo es React.ReactNode: el tipo más general de "cualquier cosa que React
puede renderizar".
interface TarjetaProps {
titulo: string;
children: React.ReactNode;
}
function Tarjeta({ titulo, children }: TarjetaProps) {
return (
<section>
<h2>{titulo}</h2>
<div>{children}</div>
</section>
);
}
// Uso:
<Tarjeta titulo="Hola">
<p>Cualquier contenido va aquí.</p>
</Tarjeta>
Regla mental: usa
React.ReactNodeparachildreny para cualquier prop que reciba "contenido renderizable". Para una prop que sea un elemento concreto, existeReact.ReactElement.