Kodokon kodokon.com

Suspense, lazy et code splitting

Maîtrisez le mécanisme de suspension, le chargement paresseux des composants et le découpage du bundle.

9 min · 3 questions

Ouvrir cette leçon dans Kodokon

Suspense repose sur un mécanisme peu connu : un composant qui n'a pas encore ses données ou son code suspend son rendu. Sous le capot, l'implémentation actuelle lève (throw) une promesse, que React intercepte comme il intercepterait une erreur. React remonte alors jusqu'à la frontière <Suspense> la plus proche, affiche son fallback, puis rejoue le rendu du sous-arbre quand la promesse se résout. C'est un détail d'implémentation (ne levez jamais de promesse vous-même), mais il explique tout le comportement observable : la frontière agit comme un catch déclaratif pour l'asynchrone. Le cas d'usage historique est le code splitting avec React.lazy.

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

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

function Dashboard() {
  return (
    <Suspense fallback={<Spinner />}>
      <Chart points={data} />
    </Suspense>
  );
}
Le code de Chart n'est téléchargé qu'au premier rendu.

lazy attend une fonction qui retourne une promesse résolue en module possédant un export default. Le découpage lui-même est l'affaire du bundler : chaque import() dynamique devient un point de coupe et produit un chunk séparé, chargé à la demande. Subtilité fréquente en base de code réelle : vos composants sont souvent des exports nommés. Il faut alors adapter le module à la volée.

JAVASCRIPT
const Chart = lazy(() =>
  import('./charts').then((module) => ({
    default: module.Chart,
  }))
);
Adapter un export nommé au contrat de React.lazy.

Le point délicat en production : lors d'une navigation, un contenu déjà affiché peut suspendre à nouveau (nouvelle route, nouveau chunk) et être brutalement remplacé par le fallback. Depuis React 18, une mise à jour enveloppée dans startTransition change ce comportement : React conserve l'interface précédente à l'écran pendant que le nouveau rendu suspend en arrière-plan, et ne montre le fallback que pour du contenu jamais révélé. Les frontières se composent aussi en profondeur : plusieurs <Suspense> imbriqués permettent de révéler l'interface par zones, du squelette global vers les détails.

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

function openTab(nextTab) {
  startTransition(() => {
    setTab(nextTab);
  });
}
// L'onglet courant reste affiche pendant le
// chargement, au lieu de repasser au fallback.
Une transition évite le retour au fallback lors d'une navigation.

Quiz de validation

Vérifiez que vous avez bien retenu les points clés de cette leçon.

  1. Que doit retourner la fonction passée à React.lazy ?
    • Un composant React directement
    • Une promesse résolue en module avec un export default
    • Un objet { component, loading }
    • Une chaîne contenant le chemin du fichier
  2. Comment éviter qu'un contenu déjà affiché soit remplacé par le fallback quand une navigation suspend ?
    • Envelopper la mise à jour d'état dans startTransition
    • Passer fallback={null} à la frontière Suspense
    • Utiliser useEffect pour retarder la navigation
    • Supprimer la frontière Suspense parente
  3. Qui réalise concrètement le découpage du code en chunks séparés ?
    • React, pendant la phase de commit
    • Le navigateur, via le cache HTTP
    • Le bundler, à partir de chaque import() dynamique