Fractionnement du code avec React.lazy et Suspense

Vous ne devez jamais envoyer plus de code que nécessaire à vos utilisateurs. Fractionnez donc vos bundles pour éviter cela.

La méthode React.lazy facilite la division du 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 se compose généralement de nombreux composants, méthodes utilitaires et bibliothèques tierces. Si aucun effort n'est fait pour essayer de charger différentes parties d'une application uniquement lorsqu'elles sont nécessaires, un seul et grand bundle JavaScript sera envoyé à vos utilisateurs dès qu'ils chargeront la première page. Cela peut avoir un impact important sur les performances des pages.

La fonction React.lazy fournit un moyen intégré de séparer les composants d'une application en blocs JavaScript distincts 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 lié à l'envoi d'une grande charge utile JavaScript aux utilisateurs est la durée nécessaire au chargement complet de la page, en particulier sur les appareils et les connexions réseau les moins performants. C'est pourquoi le fractionnement du code et le chargement différé sont extrêmement utiles.

Toutefois, il y aura toujours un léger délai que les utilisateurs devront supporter lorsqu'un composant à 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 comme état de chargement. L'exemple suivant montre comment cela fonctionne. L'avatar n'est affiché que lorsque l'utilisateur clique sur le bouton. Une requête est alors envoyé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 constitue AvatarComponent est petit, c'est pourquoi l'icône de chargement ne s'affiche que pendant une courte période. Les composants plus volumineux peuvent prendre beaucoup plus de temps à charger, en particulier avec des connexions réseau faibles.

Pour mieux illustrer 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 les outils de développement.
  • Cliquez sur l'onglet Réseau.
  • Cliquez sur le menu déroulant Limitation, qui est défini sur Aucune limitation par défaut. Sélectionnez 3G Fast.
  • Cliquez sur le bouton Click Me (Cliquez ici) dans l'application.

L'indicateur de chargement s'affichera plus longtemps. Notez que tout le code qui constitue AvatarComponent est récupéré sous forme de bloc distinct.

Panneau &quot;Réseau&quot; des 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'il 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 le rendu de plusieurs composants tout en n'affichant qu'un seul état de chargement. Une fois que tous les composants ont fini d'être récupérés, l'utilisateur peut les voir tous affichés en même temps.

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

Sans cela, il est facile de rencontrer le problème de chargement échelonné, ou de différentes parties d'une UI se chargeant les unes après les autres, 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 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 est possible que vous soyez hors connexion ou que votre application Web tente d'effectuer un chargement différé d'une URL avec version qui est obsolète et 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 : l'utilisation 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 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 enfants tels quels s'il n'y a pas 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 pour 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 fractionné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, lorsqu'un utilisateur clique sur un bouton). La division de ces composants permet de réduire au minimum vos charges utiles JavaScript.
  3. Envisagez de fractionner tout ce qui se trouve hors écran et qui n'est pas essentiel pour l'utilisateur.