Fractionnement du code avec React.lazy et Suspense

Vous n'avez jamais besoin d'envoyer plus de code que nécessaire à vos utilisateurs. Par conséquent, divisez vos lots pour éviter que cela ne se produise.

La méthode React.lazy facilite la division du code d'une application React au niveau du composant à l'aide d'importations dynamiques.

import React, { lazy } from 'react';

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

const DetailsComponent = () => (
  <div>
    <AvatarComponent />
  </div>
)

En quoi est-ce utile ?

Une application React volumineuse se compose généralement de nombreux composants, de méthodes utilitaires et de bibliothèques tierces. Si vous n'essayez pas de charger différentes parties d'une application uniquement lorsque cela est nécessaire, un seul lot volumineux de code JavaScript est envoyé aux utilisateurs dès qu'ils chargent la première page. Cela peut affecter considérablement les performances de la page.

La fonction React.lazy permet de séparer les composants d'une application en fragments JavaScript distincts avec très peu de travail. Vous pouvez ensuite gérer les états de chargement lorsque vous l'associez au composant Suspense.

Suspense

Le problème lors de l'envoi d'une charge utile JavaScript volumineuse aux utilisateurs est le temps qu'il faudrait pour que la page se charge complètement, en particulier sur les appareils et les connexions réseau plus faibles. C'est pourquoi le fractionnement de code et le chargement différé sont extrêmement utiles.

Toutefois, il y aura toujours un léger retard pour les utilisateurs lorsqu'un composant de fractionnement de code est récupéré sur le réseau. Il est donc important d'afficher un état de chargement utile. L'utilisation de React.lazy avec le composant Suspense permet de résoudre ce problème.

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

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

const renderLoader = () => <p>Loading</p>;

const DetailsComponent = () => (
  <Suspense fallback={renderLoader()}>
    <AvatarComponent />
  </Suspense>
)

Suspense accepte un composant fallback qui vous permet d'afficher n'importe quel composant React en tant qu'état de chargement. L'exemple suivant montre comment cela fonctionne. L'avatar n'est affiché que lorsque l'utilisateur clique sur le bouton, puis une requête est effectuée pour récupérer le code nécessaire au AvatarComponent suspendu. En attendant, le composant de chargement de remplacement s'affiche.

Ici, le code qui compose AvatarComponent est petit, ce qui explique pourquoi l'icône de chargement ne s'affiche que pendant une courte période. Le chargement de composants plus volumineux peut prendre beaucoup plus de temps, en particulier sur les connexions réseau faibles.

Pour mieux illustrer le fonctionnement, procédez comme suit:

  • Pour prévisualiser le site, appuyez sur Afficher l'application, puis sur Plein écran plein écran.
  • Appuyez sur Ctrl+Maj+J (ou Cmd+Option+J sur Mac) pour ouvrir les outils de développement.
  • Cliquez sur l'onglet Réseau.
  • Cliquez sur le menu déroulant Throttling (Limitation) défini par défaut sur No throttling (Aucune limitation). Sélectionnez 3G rapide.
  • Dans l'application, cliquez sur le bouton Cliquez ici.

L'indicateur de chargement reste affiché plus longtemps. Notez que tout le code qui compose le AvatarComponent est extrait en tant que fragment distinct.

Panneau du réseau DevTools montrant un fichier fragment.js en cours de téléchargement

Suspendre plusieurs composants

Suspense permet également de suspendre le chargement de plusieurs composants, même s'ils sont tous chargés de manière différée.

Exemple :

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

const AvatarComponent = lazy(() => import('./AvatarComponent'));
const InfoComponent = lazy(() => import('./InfoComponent'));
const MoreInfoComponent = lazy(() => import('./MoreInfoComponent'));

const renderLoader = () => <p>Loading</p>;

const DetailsComponent = () => (
  <Suspense fallback={renderLoader()}>
    <AvatarComponent />
    <InfoComponent />
    <MoreInfoComponent />
  </Suspense>
)

C'est un moyen extrêmement utile de retarder l'affichage de plusieurs composants tout en n'affichant qu'un seul état de chargement. Une fois l'extraction de tous les composants terminée, l'utilisateur peut les voir tous affichés en même temps.

Vous pouvez le voir avec l'intégration suivante:

Sans cela, il est facile de rencontrer le problème de chargement échelonné ou de chargement de différentes parties d'une interface utilisateur les unes après les autres, chacune ayant son propre indicateur de chargement. Cela peut rendre l’expérience utilisateur plus pénible.

Gérer les échecs de chargement

Suspense vous permet d'afficher un état de chargement temporaire pendant que les requêtes réseau sont effectuées en arrière-plan. Mais que se passe-t-il si ces requêtes réseau échouent pour une raison quelconque ? Il se peut que vous soyez hors connexion ou que votre application Web tente de charger une URL avec gestion des versions obsolète qui n'est plus à jour et qui n'est plus disponible après un redéploiement du serveur.

React dispose d'un modèle standard pour gérer correctement ces types d'échecs de chargement, en utilisant une limite d'erreur. Comme décrit dans la documentation, n'importe quel composant React peut servir de limite d'erreur s'il implémente l'une ou l'autre des méthodes de cycle de vie static getDerivedStateFromError() ou componentDidCatch() (ou les deux).

Pour détecter et gérer les échecs de chargement différé, vous pouvez encapsuler votre composant Suspense avec un composant parent qui sert de limite d'erreur. Dans la méthode render() de la limite d'erreur, vous pouvez afficher les éléments enfants tels quels en l'absence d'erreur, ou afficher un message d'erreur personnalisé en cas de problème:

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

const AvatarComponent = lazy(() => import('./AvatarComponent'));
const InfoComponent = lazy(() => import('./InfoComponent'));
const MoreInfoComponent = lazy(() => import('./MoreInfoComponent'));

const renderLoader = () => <p>Loading</p>;

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = {hasError: false};
  }

  static getDerivedStateFromError(error) {
    return {hasError: true};
  }

  render() {
    if (this.state.hasError) {
      return <p>Loading failed! Please reload.</p>;
    }

    return this.props.children;
  }
}

const DetailsComponent = () => (
  <ErrorBoundary>
    <Suspense fallback={renderLoader()}>
      <AvatarComponent />
      <InfoComponent />
      <MoreInfoComponent />
    </Suspense>
  </ErrorBoundary>
)

Conclusion

Si vous ne savez pas par où commencer à appliquer le fractionnement de code à votre application React, procédez comme suit:

  1. Commencez au niveau de la route. Les routes sont le moyen le plus simple d'identifier les points de votre application qui peuvent être divisés. La documentation React montre comment Suspense peut être utilisé avec react-router.
  2. Identifiez tous les composants volumineux d'une page de votre site qui ne s'affichent que lors de certaines interactions utilisateur (comme cliquer sur un bouton). La division de ces composants réduit les charges utiles JavaScript.
  3. Envisagez de diviser tout autre élément qui se trouve hors de l'écran et qui n'est pas essentiel pour l'utilisateur.