Suddivisione del codice con React.lazy e Suspense

Non devi mai inviare agli utenti più codice del necessario, quindi dividi i bundle per assicurarti che ciò 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 di solito è composta da molti componenti, metodi utilitari e librerie di terze parti. Se non viene fatto alcuno sforzo per caricare diverse parti di un'applicazione solo quando sono necessarie, un unico e grande bundle di JavaScript verrà inviato agli utenti non appena caricano la prima pagina. Ciò può influire in modo significativo sul rendimento della pagina.

La funzione React.lazy fornisce un modo integrato per separare i componenti di un'applicazione in blocchi separati di JavaScript con un lavoro minimo. Puoi quindi occuparti degli stati di caricamento quando lo accoppi al componente Suspense.

Thriller

Il problema con l'invio di un payload JavaScript di grandi dimensioni agli utenti è la durata del caricamento della pagina, soprattutto su dispositivi e connessioni di rete più deboli. Ecco perché la suddivisione del codice e il caricamento differito sono estremamente utili.

Tuttavia, ci sarà sempre un leggero ritardo che gli utenti dovranno sperimentare quando un componente con suddivisione del codice viene recuperato dalla rete, quindi è importante visualizzare uno stato di caricamento utile. L'utilizzo di React.lazy con il componente Suspense aiuta a risolvere questo 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 React come stato di caricamento. L'esempio seguente mostra come funziona. L'avatar viene visualizzato solo quando viene fatto clic sul pulsante, quando viene effettuata una richiesta per recuperare il codice necessario per il AvatarComponent sospeso. Nel frattempo, viene mostrato il componente di caricamento di riserva.

Qui, il codice che compone AvatarComponent è piccolo, motivo per cui l'indicatore di caricamento viene visualizzato solo per un breve periodo di tempo. I componenti più grandi possono richiedere molto più tempo per il caricamento, soprattutto con connessioni di rete deboli.

Per dimostrare meglio come funziona:

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

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

Il riquadro Rete di DevTools che mostra il download di un file chunk.js

Sospensione di più componenti

Un'altra funzionalità di Suspense è che ti consente di sospendere il caricamento di più componenti, anche se sono tutti caricati in modalità differita.

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 un solo stato di caricamento. Una volta completato il recupero di tutti i componenti, l'utente li vedrà visualizzati contemporaneamente.

Puoi vedere questo con il seguente incorporamento:

Senza questo, è facile imbattersi nel problema del caricamento scaglionato o del caricamento di diverse parti di un'interfaccia utente una dopo l'altra, ognuna con il proprio indicatore di caricamento. In questo modo, l'esperienza utente può risultare più brusca.

Gestire gli errori di caricamento

Suspense ti consente di visualizzare uno stato di caricamento temporaneo mentre le richieste di rete vengono effettuate in background. Ma cosa succede se queste richieste di rete non vanno a buon fine per qualche motivo? Potresti essere offline o la tua app web potrebbe tentare di caricare in modalità differita un URL con controllo delle versioni obsoleto e non più disponibile dopo un nuovo deployment del server.

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

Per rilevare e gestire gli errori di caricamento differito, puoi racchiudere il componente Suspense con un componente padre che funge da limite di errore. All'interno del metodo render() del limite di errore, puoi eseguire il rendering dei figli così come sono se non si verifica alcun errore oppure eseguire il rendering di un messaggio di errore personalizzato in caso di problemi:

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. La documentazione di React mostra come utilizzare Suspense insieme a react-router.
  2. Identifica eventuali componenti di grandi dimensioni in una pagina del tuo sito che vengono visualizzati solo in determinate interazioni dell'utente (ad esempio, quando fa clic su un pulsante). La suddivisione di questi componenti ridurrà al minimo i payload JavaScript.
  3. Valuta la possibilità di dividere qualsiasi altro elemento che non è visibile sullo schermo e non è fondamentale per l'utente.