Skip to content
This page has been auto-translated and may contain errors.View in English

Divisão de código

Por padrão, um bundler junta toda a sua app em um único arquivo JavaScript, e cada import no início de um arquivo contribui para ele. Esse bundle único cresce com a app, e o navegador precisa baixar tudo antes de qualquer coisa aparecer na tela.

Em uma máquina rápida com Wi-Fi forte você pode nunca notar. Em um celular com conexão fraca, uma biblioteca pesada consegue bloquear todo o carregamento inicial, inclusive recursos que não precisam nada dessa biblioteca. O curso demonstra isso com uma lista de produtos que depende de um grande pacote de dados fake: com tudo em um bundle, até o contador na mesma página fica em branco por mais de vinte segundos em uma conexão limitada.

Divisão de código é a solução. Ela quebra o bundle em pedaços, chamados chunks, e baixa um chunk apenas se e quando o usuário realmente precisa dele. Recursos que o usuário nunca usa nunca são baixados.

Dynamic import()

A primitiva por trás disso é JavaScript puro. Um import no topo do arquivo sempre é executado. Chamar import() como uma função carrega um módulo sob demanda, de qualquer lugar do seu código, e retorna uma promise que se resolve com o módulo.

jsx
// sempre carregado, parte do bundle principal
import { formatPrice } from './format'

// carregado apenas quando esse handler executa
async function showChart() {
  const { renderChart } = await import('./charts')
  renderChart()
}

Bundlers tratam cada chamada de import() como uma linha de corte: o módulo importado, mais as dependências que ele não compartilha com o bundle principal, recebem seu próprio chunk, buscado pela rede na primeira vez que a chamada é executada.

lazy e Suspense

React envolve essa primitiva em um par de ferramentas. lazy recebe uma função que retorna um import() dinâmico e te devolve um componente que você pode renderizar como qualquer outro. A promise do loader precisa se resolver para um módulo cujo export padrão é o componente; outros named exports ao lado dele são aceitáveis. Para um arquivo que você não controla e que exporta apenas componentes nomeados, adapte a promise assim: lazy(() => import('./charts').then(m => ({ default: m.RevenueChart }))).

Renderizar um componente lazy suspende: React pausa a renderização dessa parte da árvore até o chunk chegar. Sozinho isso é um erro, então você envolve o componente lazy em um Suspense boundary com uma prop fallback, alguma UI para mostrar enquanto o download está em progresso.

jsx
import { lazy, Suspense, useState } from 'react'

const ProductsList = lazy(() => import('./ProductsList'))

export default function App() {
  const [showProducts, setShowProducts] = useState(false)

  return (
    <>
      <Counter />
      <button onClick={() => setShowProducts(prev => !prev)}>
        Mostrar produtos
      </button>
      {showProducts && (
        <Suspense fallback={<h2>Carregando...</h2>}>
          <ProductsList />
        </Suspense>
      )}
    </>
  )
}

Agora o bundle principal não contém mais ProductsList nem sua dependência pesada. O contador aparece imediatamente. Clicar no botão inicia o download do chunk, o fallback mostra enquanto carrega, e o componente renderiza quando chega. Após esse primeiro carregamento o módulo é cachado, então esconder e mostrar novamente é instantâneo.

Nota de versão

lazy e Suspense são named imports de react. Código mais antigo, incluindo as aulas do curso, escreve eles como React.lazy e React.Suspense; são as mesmas APIs.

Divida por rotas primeiro

Você poderia procurar por componentes individuais pesados, mas a linha de corte padrão na maioria das apps é a rota. Cada página é uma unidade natural: um visitante na página inicial não tem uso para o código do dashboard, então faça cada página um componente lazy e deixe o roteamento decidir quais chunks são baixados.

jsx
import { lazy, Suspense } from 'react'
import { BrowserRouter, Routes, Route } from 'react-router-dom'

const Home = lazy(() => import('./pages/Home'))
const Vans = lazy(() => import('./pages/Vans'))
const Dashboard = lazy(() => import('./pages/Dashboard'))

export default function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<h2>Carregando...</h2>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/vans" element={<Vans />} />
          <Route path="/host" element={<Dashboard />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  )
}

Um Suspense boundary ao redor das rotas cobre cada página. O carregamento inicial busca o bundle principal mais apenas o chunk para a URL atual; outras páginas são baixadas quando o usuário navega para elas. Essa amplitude tem um custo: navegar para uma página cujo chunk não chegou ainda substitui toda a área da rota pelo fallback, então mantenha o layout compartilhado como header e nav fora do boundary e eles ficam no lugar durante a troca.

Divisão tem um limite, porém. Cada chunk custa uma requisição de rede extra e um momento de UI de fallback, então dividir um componente minúsculo não economiza nada que valha esse preço. Deixe componentes pequenos e qualquer coisa acima da dobra que os usuários veem imediatamente no bundle principal. Divida o que é pesado, raramente usado, ou atrás de uma navegação.

A divisão acontece em tempo de build. O bundler encontra estaticamente cada chamada de import(), emite um chunk separado para aquele grafo de módulos, e reescreve a chamada em uma busca para a URL daquele chunk.

lazy em si é simples: mantém a promise da sua função loader e, na primeira renderização, a lança, o que é o sinal de suspension. React a captura, sobe até o Suspense boundary mais próximo, faz commit do fallback, e tenta renderizar novamente uma vez que a promise se resolve. O loader executa uma vez; depois disso o componente resolvido é cachado no wrapper lazy e renderiza sincronamente, e o cache do navegador faz revisitas baratas até mesmo entre recarregamentos.

Placement do boundary é uma decisão de experiência do usuário. Um único boundary perto da raiz significa que qualquer descendente que suspende apaga uma região grande e pode causar layout shift quando o conteúdo chega; um boundary escopo fechado ao redor da subtree lazy mantém o resto da página interativa, o que é a escolha que o curso faz.

A tabela de rotas é a exceção que reconcilia as duas regras: um boundary amplo lá merece seu lugar porque uma navegação substitui aquela região inteira de qualquer forma, e o fallback fica no lugar de uma página que estava prestes a ser trocada. Dentro de uma página, escope fechado.

Quando um chunk falha em carregar

Planeje para o chunk que nunca chega. Um fetch falhado rejeita a promise do loader, e Suspense cobre apenas o estado pendente: React relança a rejeição durante render, e um render error que nada captura desmonta toda a árvore.

Pareie o boundary com um error boundary, um componente que captura um render error abaixo dele e mostra UI de fallback em seu lugar. A versão built-in do React é a única coisa neste handbook que ainda precisa ser um class component, já que não há equivalente com hook, é por isso que a maioria dos projetos instala react-error-boundary e envolve as rotas com seu ErrorBoundary.

A causa usual é um deploy passado de data, onde uma aba aberta requisita um chunk com hash que o novo build não serve mais, e o que o usuário precisa lá é um page reload ao invés de um retry.

Fique atento a cachoeiras também: uma rota lazy que começa a buscar dados apenas depois que seu chunk chega serializa duas round trips. As APIs de data do React Router, onde um loader de rota é executado antes de renderizar, ou preloading por intenção (começando o import() no hover do link ou focus, antes da renderização precisar) colapsam isso. Chamar o mesmo import('./ProductsList') cedo é seguro; o registry de módulos a deduplica, e a renderização lazy depois encontra o chunk já em progresso ou pronto.

JunoBaixe código apenas quando necessário Normalmente o navegador precisa baixar todo o JavaScript da sua app antes de mostrar qualquer coisa, e um recurso grande consegue tomar todo o resto como refém. Divisão de código permite você dizer "carregue essa parte depois, apenas se o usuário pedir".

Você envolve um import dinâmico em lazy, coloca o componente dentro de Suspense com um fallback como uma mensagem de carregamento, e React mostra o fallback enquanto o código baixa. O resto da sua app aparece imediatamente.

JunoBaixe código apenas quando necessárioconst Page = lazy(() => import('./Page')) te dá um componente cujo código vive em seu próprio chunk, buscado na primeira renderização; o arquivo deve fazer export padrão do componente, e um Suspense boundary com um fallback cobre o download.

Faça rotas seus pontos de divisão padrão: cada página um componente lazy, um boundary ao redor da tabela de rotas. Pule dividir componentes minúsculos e UI acima da dobra, já que cada chunk custa uma requisição.

JunoBaixe código apenas quando necessário O bundler corta um chunk em cada site de chamada de import() em tempo de build; lazy lança a promise do loader na primeira renderização, o Suspense boundary mais próximo mostra seu fallback, e o módulo resolvido é cachado para que renderizações posteriores sejam síncronas.

Escope boundaries para controlar quanta UI uma suspension apaga, e preload por intenção ou deixe o router buscar dados de uma rota antes de renderizar para evitar cachoeiras chunk-então-dados. Chamadas de import() duplicadas se deduplicam, então aquecer um chunk cedo é grátis.

Próximo: Autenticação, onde sessions, tokens, e aplicação do lado do servidor se unem.