DevPath · Aprende a programar ESPTEN

React profesional: TypeScript, SSR y accesibilidad

React + TypeScript: tipar tus componentes

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>;
}

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 (declaras children solo 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.ReactNode para children y para cualquier prop que reciba "contenido renderizable". Para una prop que sea un elemento concreto, existe React.ReactElement.

Pon esto en práctica

DevPath es un curso práctico: aquí lees la teoría; en la app la pones en práctica con ejercicios que se ejecutan de verdad, sin conexión.

Empezar gratis en la app →
Renderizado en servidor: CSR, SSR, SSG y Server Components →