Divisão de código com React.Lazy e Suspense

Você nunca precisa enviar mais código do que o necessário para os usuários. Por isso, divida seus pacotes para garantir que isso nunca aconteça.

O método React.lazy facilita a divisão de código de um aplicativo React em um nível de componente usando importações dinâmicas.

import React, { lazy } from 'react';

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

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

Por que isso é útil?

Um aplicativo React grande geralmente consiste em muitos componentes, métodos utilitários e bibliotecas de terceiros. Se não houver um esforço para tentar carregar partes diferentes de um aplicativo somente quando necessário, um único pacote grande de JavaScript será enviado aos usuários assim que eles carregarem a primeira página. Isso pode afetar significativamente o desempenho da página.

A função React.lazy oferece uma maneira integrada de separar componentes em um aplicativo em blocos separados de JavaScript com pouquíssimo trabalho. Em seguida, você pode cuidar dos estados de carregamento ao acoplá-lo ao componente Suspense.

Suspense

O problema de enviar um payload grande de JavaScript aos usuários é o tempo que a página leva para carregar, especialmente em dispositivos e conexões de rede mais lentos. É por isso que a divisão de código e o carregamento lento são extremamente úteis.

No entanto, sempre haverá um pequeno atraso que os usuários terão que experimentar quando um componente de divisão de código estiver sendo buscado na rede. Por isso, é importante mostrar um estado de carregamento útil. O uso de React.lazy com o componente Suspense ajuda a resolver esse 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 aceita um componente fallback que permite exibir qualquer componente do React como um estado de carregamento. O exemplo a seguir mostra como isso funciona. O avatar só é renderizado quando o botão é clicado, o que faz uma solicitação para recuperar o código necessário para o AvatarComponent suspenso. Enquanto isso, o componente de carregamento substituto é mostrado.

O código que compõe o AvatarComponent é pequeno, e é por isso que o ícone de carregamento aparece apenas por um curto período. Componentes maiores podem levar muito mais tempo para carregar, especialmente em conexões de rede fracas.

Para demonstrar melhor como isso funciona:

  • Para visualizar o site, pressione View App. Em seguida, pressione Fullscreen tela cheia.
  • Pressione "Control+Shift+J" (ou "Command+Option+J" no Mac) para abrir o DevTools.
  • Clique na guia Rede.
  • Clique no menu suspenso Limitação, que é definido como Sem limitação por padrão. Selecione 3G rápido.
  • Clique no botão Click Me no app.

O indicador de carregamento vai aparecer por mais tempo agora. Observe como todo o código que compõe o AvatarComponent é buscado como um bloco separado.

Painel Network do DevTools mostrando o download de um arquivo chunk.js

Como suspender vários componentes

Outro recurso do Suspense é que ele permite suspender o lazy loading de vários componentes, mesmo que todos sejam lazy loaded.

Exemplo:

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

Essa é uma maneira extremamente útil de atrasar a renderização de vários componentes e, ao mesmo tempo, mostrar apenas um estado de carregamento. Depois que todos os componentes terminarem de buscar, o usuário vai ver todos eles exibidos ao mesmo tempo.

Confira isso com a seguinte incorporação:

Sem isso, é fácil se deparar com o problema de carregamento escalonado ou partes diferentes de uma interface sendo carregadas uma após a outra, cada uma com o próprio indicador de carregamento. Isso pode tornar a experiência do usuário mais desagradável.

Processar falhas de carregamento

O Suspense permite mostrar um estado de carregamento temporário enquanto as solicitações de rede são feitas em segundo plano. Mas e se essas solicitações de rede falharem por algum motivo? Talvez você esteja off-line ou seu app da Web esteja tentando fazer o carregamento lento de um URL com controle de versão desatualizado e que não está mais disponível após uma reimplantação do servidor.

O React tem um padrão para lidar com esses tipos de falhas de carregamento: usar um limite de erro. Conforme descrito na documentação, qualquer componente do React pode servir como um limite de erro se implementar um (ou ambos) dos métodos de ciclo de vida static getDerivedStateFromError() ou componentDidCatch().

Para detectar e processar falhas de carregamento lento, envolva o componente Suspense com um componente pai que sirva como um limite de erro. No método render() do limite de erro, é possível renderizar os filhos como estão, se não houver erro, ou renderizar uma mensagem de erro personalizada se algo der errado:

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

Conclusão

Se você não souber por onde começar a aplicar a divisão de código ao seu aplicativo React, siga estas etapas:

  1. Comece no nível da rota. As rotas são a maneira mais simples de identificar pontos do aplicativo que podem ser divididos. A documentação do React mostra como Suspense pode ser usado com react-router.
  2. Identifique todos os componentes grandes em uma página do seu site que só são renderizados em determinadas interações do usuário (como clicar em um botão). Dividir esses componentes vai minimizar os payloads do JavaScript.
  3. Considere dividir qualquer outro elemento que esteja fora da tela e não seja crítico para o usuário.