Fractionnement du code avec React.lazy et Suspense

Vous n'avez jamais besoin de fournir plus de code que nécessaire à vos utilisateurs, alors divisez vos bundles pour éviter que cela n'arrive.

La méthode React.lazy permet de diviser facilement le code d'une application React au niveau des composants à 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 grande application React comprend généralement de nombreux composants, méthodes d'utilité et bibliothèques tierces. S'il n'est pas possible de charger différentes parties d'une application uniquement lorsqu'elles sont nécessaires, un seul bundle de JavaScript volumineux sera envoyé aux utilisateurs dès qu'ils chargent la première page. Cela peut avoir un impact significatif sur les performances de la page.

La fonction React.lazy fournit un moyen intégré de séparer les composants d'une application en blocs distincts de code JavaScript avec très peu d'efforts. Vous pouvez ensuite gérer les états de chargement lorsque vous l'associez au composant Suspense.

Suspense

Le problème de l'envoi d'une charge utile JavaScript importante aux utilisateurs est la durée de chargement de la page, en particulier sur les appareils et les connexions réseau moins performants. C'est pourquoi le fractionnement du code et le chargement différé sont extrêmement utiles.

Toutefois, les utilisateurs devront toujours subir un léger retard lorsqu'un composant de fractionnement de code est extrait 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 procéder. L'avatar n'est affiché que lorsque vous cliquez sur le bouton, où une requête est alors effectuée pour récupérer le code nécessaire pour l'AvatarComponent suspendue. En attendant, le composant de chargement de remplacement s'affiche.

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

Pour mieux comprendre ce fonctionnement:

  • 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 DevTools.
  • Cliquez sur l'onglet Réseau.
  • Cliquez sur le menu déroulant Limitation, qui est défini sur Pas de limitation par défaut. Sélectionnez 3G rapide.
  • Cliquez sur le bouton Cliquez ici dans l'application.

L'indicateur de chargement s'affiche encore plus longtemps. Notez que tout le code qui compose AvatarComponent est récupéré sous la forme d'un bloc distinct.

Panneau &quot;Réseau&quot; de la console d&#39;outils de développement montrant le téléchargement d&#39;un fichier chunk.js

Suspendre plusieurs composants

Une autre fonctionnalité de Suspense est qu'elle vous permet 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>
)

Il s'agit d'un moyen extrêmement utile de retarder l'affichage de plusieurs composants tout en n'affichant qu'un seul état de chargement. Une fois que tous les composants ont été récupérés, l'utilisateur peut les voir tous affichés en même temps.

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

Sans cela, vous risquez de rencontrer le problème de chargement fractionné, ou de voir différentes parties d'une UI se charger l'une après l'autre, chacune ayant son propre indicateur de chargement. Cela peut rendre l'expérience utilisateur plus désagréable.

Gérer les échecs de chargement

Suspense vous permet d'afficher un état de chargement temporaire pendant que des 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 ? Vous êtes peut-être hors connexion, ou votre application Web tente de charger de manière différée une URL versionnée obsolète et non plus disponible après un redéploiement de serveur.

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

Pour détecter et gérer les échecs de chargement paresseux, 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 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 du code à votre application React, procédez comme suit:

  1. Commencez au niveau de l'itinéraire. 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 utiliser Suspense avec react-router.
  2. Identifiez les composants volumineux d'une page de votre site qui ne s'affichent que lors de certaines interactions utilisateur (par exemple, lorsque l'utilisateur clique sur un bouton). En divisant ces composants, vous réduirez vos charges utiles JavaScript.
  3. Envisagez de diviser tout autre élément qui n'est pas à l'écran et qui n'est pas essentiel pour l'utilisateur.