Suddivisione del codice con React.lazy e Suspense

Non è mai necessario inviare agli utenti più codice del necessario, quindi dividi i tuoi bundle per assicurarti che non accada mai.

Il metodo React.lazy semplifica la suddivisione del codice di un'applicazione React a livello di componente utilizzando le importazioni dinamiche.

import React, { lazy } from 'react';

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

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

Perché è utile?

Un'applicazione React di grandi dimensioni è generalmente costituita da molti componenti, metodi di utilità e librerie di terze parti. Se non fai il possibile per caricare parti diverse di un'applicazione solo quando sono necessarie, gli utenti riceveranno un unico e grande bundle di JavaScript non appena viene caricata la prima pagina. Ciò può influire notevolmente sulle prestazioni della pagina.

La funzione React.lazy offre un modo integrato per separare i componenti di un'applicazione in blocchi separati di JavaScript con pochissimo lavoro. Puoi quindi gestire gli stati di caricamento quando lo accoppi al componente Suspense.

Thriller

Il problema dell'invio agli utenti di un payload JavaScript di grandi dimensioni è il tempo necessario per completare il caricamento della pagina, in particolare sui dispositivi e sulle connessioni di rete più deboli. Per questo motivo la suddivisione del codice e il caricamento lento sono estremamente utili.

Tuttavia, ci sarà sempre un leggero ritardo che gli utenti devono riscontrare durante il recupero di un componente di suddivisione del codice sulla rete, perciò è importante visualizzare uno stato di caricamento utile. L'utilizzo di React.lazy con il componente Suspense consente di risolvere il problema.

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

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

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

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

Suspense accetta un componente fallback che ti consente di visualizzare qualsiasi componente di React come stato di caricamento. L'esempio seguente mostra come funziona. L'avatar viene visualizzato solo quando si fa clic sul pulsante, dove viene poi effettuata una richiesta per recuperare il codice necessario per l'oggetto AvatarComponent sospeso. Nel frattempo, viene mostrato il componente di caricamento di riserva.

In questo caso, il codice di AvatarComponent è piccolo ed è per questo che la rotellina di caricamento viene visualizzata solo per un breve periodo di tempo. Il caricamento dei componenti più grandi può richiedere molto più tempo, in particolare nelle connessioni di rete deboli.

Per dimostrare meglio come funziona:

  • Per visualizzare l'anteprima del sito, premi Visualizza app, quindi Schermo intero schermo intero.
  • Premi "Control+Maiusc+J" (o "Comando+Opzione+J" su Mac) per aprire DevTools.
  • Fai clic sulla scheda Rete.
  • Fai clic sul menu a discesa Minore, che è impostato su Nessuna limitazione per impostazione predefinita. Seleziona 3G veloce.
  • Fai clic sul pulsante Click Me (Click Me) nell'app.

L'indicatore di caricamento verrà visualizzato più a lungo. Osserva come tutto il codice che costituisce AvatarComponent viene recuperato come blocco separato.

Riquadro di rete DevTools che mostra un file chunk.js in fase di download

Sospensione di più componenti

Un'altra funzionalità di Suspense è che consente di sospendere il caricamento di più componenti, anche se sono tutti caricati tramite caricamento lento.

Ad esempio:

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>
)

Si tratta di un modo estremamente utile per ritardare il rendering di più componenti, mostrando contemporaneamente un solo stato di caricamento. Al termine del recupero di tutti i componenti, l'utente può vederli tutti contemporaneamente.

Puoi vederlo con il seguente elemento incorporato:

Senza questo strumento, è facile incorrere nel problema del caricamento scaglionato o del caricamento delle diverse parti di una UI l'una dopo l'altra, ciascuno con un proprio indicatore di caricamento. In questo modo, l'esperienza utente potrebbe apparire più sgradevole.

Gestire gli errori di caricamento

Suspense consente di mostrare uno stato di caricamento temporaneo mentre le richieste di rete vengono effettuate in dettaglio. Ma cosa succede se le richieste di rete non vanno a buon fine? Potresti essere offline o forse la tua app web sta tentando di eseguire il caricamento lento di un URL versione che non è aggiornato e non è più disponibile in seguito al nuovo deployment del server.

React ha un pattern standard per gestire agevolmente questi tipi di errori di caricamento: l'utilizzo di un limite di errore. Come descritto nella documentazione, qualsiasi componente di React può fungere da limite di errore se implementa uno o entrambi i metodi del ciclo di vita static getDerivedStateFromError() o componentDidCatch().

Per rilevare e gestire gli errori di caricamento lento, puoi eseguire il wrapping del componente Suspense con un componente padre che funga da limite di errore. All'interno del metodo render() del confine di errore, puoi visualizzare gli elementi secondari così come sono se non ci sono errori, oppure visualizzare un messaggio di errore personalizzato se si verifica un problema:

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>
)

Conclusione

Se non sai da dove iniziare ad applicare la suddivisione del codice alla tua applicazione React, segui questi passaggi:

  1. Inizia a livello di percorso. Le route sono il modo più semplice per identificare i punti dell'applicazione che possono essere suddivisi. I documenti di React mostrano come utilizzare Suspense insieme a react-router.
  2. Identifica tutti i componenti di grandi dimensioni di una pagina del tuo sito che vengono visualizzati solo in base a determinate interazioni degli utenti, ad esempio i clic su un pulsante. La suddivisione di questi componenti ridurrà al minimo i payload JavaScript.
  3. Valuta la possibilità di dividere tutti gli altri elementi fuori schermo e non critici per l'utente.