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

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

O método React.lazy facilita a divisão de código de um aplicativo React no nível do 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 grande do React geralmente consiste em muitos componentes, métodos utilitários e bibliotecas de terceiros. Se não houver esforço para tentar carregar partes diferentes 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 o desempenho da página.

A função React.lazy oferece uma maneira integrada de separar componentes de um aplicativo em partes distintas de JavaScript com muito pouco trabalho legislativo. Você pode cuidar dos estados de carregamento ao juntá-lo ao componente Suspense.

Suspense

O problema de enviar um grande payload de JavaScript para os usuários é o tempo necessário para que a página termine de carregar, especialmente em dispositivos e conexões de rede mais fracos. É 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 mostrar qualquer componente do React como um estado de carregamento. O exemplo abaixo mostra como isso funciona. O avatar só é renderizado quando o botão é clicado. Em seguida, é feita uma solicitação para extrair o código necessário para o AvatarComponent suspenso. Enquanto isso, o componente de carregamento substituto é mostrado.

Aqui, o código que compõe a AvatarComponent é pequeno. É por isso que o ícone de carregamento é exibido por um curto período. Componentes maiores podem levar muito mais tempo para serem carregados, especialmente em conexões de rede fracas.

Para demonstrar melhor como isso funciona:

  • Para visualizar o site, pressione Ver app. Em seguida, pressione Tela cheia modo 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 por padrão como Sem limitaçã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 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 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 todos os componentes terminarem a busca, o usuário poderá ver todos eles ao mesmo tempo.

Você pode ver isso com a seguinte incorporação:

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

Solucionar falhas de carregamento

Suspense permite que você mostre 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? Você pode estar off-line ou talvez seu app da Web esteja tentando carregar lentamente um URL com controle de versão que está desatualizado e não está mais disponível após uma reimplantação do servidor.

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

Para detectar e processar falhas de carregamento lento, una 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 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 onde começar a aplicar a divisão de código ao seu aplicativo Reação, siga estas etapas:

  1. Comece no nível do trajeto. 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 os componentes grandes de uma página do site que são renderizados somente com determinadas interações do usuário (como clicar em um botão). Dividir esses componentes minimizará os payloads do JavaScript.
  3. Divida tudo o que esteja fora da tela e não essencial para o usuário.