Kodokon kodokon.com

Suspense, lazy y code splitting

Domina el mecanismo de suspensión, la carga diferida de componentes y la división del bundle.

9 min · 3 preguntas

Abrir esta lección en Kodokon

Suspense se apoya en un mecanismo poco conocido: un componente que aún no tiene sus datos o su código suspende su renderizado. Bajo el capó, la implementación actual lanza una promesa, que React intercepta igual que interceptaría un error. React sube entonces hasta el límite <Suspense> más cercano, muestra su fallback, y luego reproduce el renderizado del subárbol una vez que la promesa se resuelve. Es un detalle de implementación (nunca lances una promesa tú mismo), pero explica todo el comportamiento observable: el límite actúa como un catch declarativo para el trabajo asíncrono. El caso de uso histórico es el code splitting con React.lazy.

JSX
import { lazy, Suspense } from 'react';

const Chart = lazy(() => import('./Chart'));

function Dashboard() {
  return (
    <Suspense fallback={<Spinner />}>
      <Chart points={data} />
    </Suspense>
  );
}
El código de Chart solo se descarga en su primer renderizado.

lazy espera una función que devuelve una promesa que resuelve a un módulo con una exportación default. La división en sí es tarea del bundler: cada import() dinámico se convierte en un punto de división y produce un chunk separado, cargado bajo demanda. Una sutileza frecuente en las bases de código reales: tus componentes suelen ser exportaciones con nombre. Entonces necesitas adaptar el módulo sobre la marcha.

JAVASCRIPT
const Chart = lazy(() =>
  import('./charts').then((module) => ({
    default: module.Chart,
  }))
);
Adaptar una exportación con nombre al contrato de React.lazy.

La parte delicada en producción: durante una navegación, un contenido que ya se muestra puede volver a suspenderse (nueva ruta, nuevo chunk) y ser reemplazado bruscamente por el fallback. Desde React 18, una actualización envuelta en startTransition cambia este comportamiento: React mantiene la interfaz anterior en pantalla mientras el nuevo renderizado se suspende en segundo plano, y solo muestra el fallback para el contenido que nunca se ha revelado. Los límites también se componen en profundidad: varios límites <Suspense> anidados permiten revelar la interfaz zona por zona, desde el esqueleto global hasta los detalles.

JSX
const [isPending, startTransition] = useTransition();

function openTab(nextTab) {
  startTransition(() => {
    setTab(nextTab);
  });
}
// The current tab stays visible while the new
// one loads, instead of dropping back to the fallback.
Una transición evita caer en el fallback durante la navegación.

Prueba de conocimientos

Comprueba que has retenido los puntos clave de esta lección.

  1. ¿Qué debe devolver la función que se pasa a React.lazy?
    • Un componente React directamente
    • Una promesa que resuelve a un módulo con una exportación default
    • Un objeto { component, loading }
    • Una cadena con la ruta del archivo
  2. ¿Cómo evitas que un contenido ya mostrado sea reemplazado por el fallback cuando una navegación se suspende?
    • Envolviendo la actualización de estado en startTransition
    • Pasando fallback={null} al límite Suspense
    • Usando useEffect para retrasar la navegación
    • Eliminando el límite Suspense padre
  3. ¿Quién realiza realmente la división del código en chunks separados?
    • React, durante la fase de commit
    • El navegador, mediante la caché HTTP
    • El bundler, a partir de cada import() dinámico