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. Portanto, 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 de utilitários e bibliotecas de terceiros. Se não for feito um esforço para carregar diferentes partes de um aplicativo somente quando elas forem necessárias, um único pacote grande de JavaScript será enviado aos usuários assim que eles carregarem a primeira página. Isso pode afetar significativamente a performance da página.

A função React.lazy oferece uma maneira integrada de separar componentes de um aplicativo em partes separadas de JavaScript com muito pouco trabalho. Em seguida, é possível cuidar dos estados de carregamento ao vincular o componente Suspense.

Suspense

O problema de enviar um payload grande de JavaScript para os usuários é o tempo que a página leva para terminar o carregamento, especialmente em dispositivos mais fracos e conexões de rede. É 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 quando um componente de divisão de código estiver sendo buscado na rede. Por isso, é importante exibir um estado de carregamento útil. Usar 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, quando uma solicitação é feita para recuperar o código necessário para o AvatarComponent suspenso. Enquanto isso, o componente de carregamento substituto é mostrado.

Aqui, o código que compõe 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. Observe como todo o código que compõe o AvatarComponent é buscado como um bloco separado.

Painel de rede do DevTools mostrando um arquivo chunk.js sendo baixado

Suspensão de vários componentes

Outro recurso do Suspense é que ele permite suspender o carregamento de vários componentes, mesmo que todos tenham carregamento lento.

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 enquanto mostra apenas um único estado de carregamento. Depois que a busca de todos os componentes for concluída, o usuário vai poder conferir todos eles ao mesmo tempo.

Confira isso com a seguinte incorporação:

Sem isso, é fácil se deparar com o problema do carregamento escalonado ou de partes diferentes de uma interface que são 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

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 carregar com carregamento lento um URL com versão que está desatualizado e não está mais disponível após uma nova implantação do servidor.

O React tem um padrão padrão para lidar com esses tipos de falhas de carregamento: usando 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, você pode agrupar o componente Suspense com um componente pai que serve como um limite de erro. Dentro do método render() do limite de erro, é possível renderizar os filhos como estão, se não houver erros, 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 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. Os documentos do React mostram como Suspense pode ser usado com react-router.
  2. Identifique componentes grandes em uma página do site que são renderizados apenas em determinadas interações do usuário (como clicar em um botão). A divisão desses componentes vai minimizar seus payloads JavaScript.
  3. Considere dividir tudo o que estiver fora da tela e não for essencial para o usuário.