Code splitting e lazy loading no React

Javascript

A maioria dos aplicativos feitos com a biblioteca React, terão seus arquivos empacotados, esse empacotamento pode ser feito com ferramentas de empacotação, por exemplo: Webpack ou Parcel (além de outras). Realizar o empacotamento de uma aplicação é o processo onde todos os arquivos importados e usados no projeto serão mergeados para um único arquivo. Esse único arquivo é onde todo o projeto foi empacotado e mesclado, ele é o responsável por carregar a aplicação inteira de uma vez e deixá-la funcionando.

Por exemplo:

Aplicativo rodando sem lazy loading

A aplicação para exemplo do post não tem nada demais, apenas um sistema de rotas, onde:

  • /: Renderiza o HomeComponent.
  • /a: Renderiza o AComponent.
  • /b: Renderiza o BComponent.
  • /c: Renderiza o CComponent.

As rotas não estão sendo visíveis, pois, preferi gravar apenas a aplicação em funcionamento e não o navegador de forma completa.

Se olharmos na aba network do navegador (no caso do Chrome), podemos ver que tudo está sendo baixado de uma única vez:

Network sem lazy loading

Mas, será que podemos melhorar nossa aplicação?

Pensando se a aplicação pode ser melhorada

Sim, podemos melhorar o desempenho e performance da mesma, assunto do tópico á seguir.

Code Splitting

Uma técnica muito famosa para resolver esse tipo de problema é chamada de Code Splitting (separação de código), como funciona essa técnica? Basicamente, pegamos nosso único arquivo .js que foi empacotado e o dividimos em outros pequenos arquivos (geralmente um para cada componente). Dessa maneira, não teremos apenas um .js com todo o código necessário para que nossa aplicação funcione de forma esperada, ao invés, teremos pequenos .js's separados:

main.js

home.js
a.js
b.js
c.js

Ao grosso modo, teremos algo parecido com o exemplo acima (não que seja assim, foi apenas um exemplo para um melhor entendimento).

Dessa maneira, conseguimos carregar os componentes de forma separada, ou seja, apenas quando forem necessários, por exemplo:

  1. Ao acessar a rota / faz o download do arquivo .js responsável pelo HomeComponent e o renderiza.
  2. Ao acessar a rota /a faz o download do arquivo .js responsável pelo AComponent e o renderiza.
  3. Ao acessar a rota /b faz o download do arquivo .js responsável pelo BComponent e o renderiza.
  4. Ao acessar a rota /c faz o download do arquivo .js responsável pelo CComponent e o renderiza.

Aqui já vemos uso de outra técnica, conhecida como Lazy Loading (carregamento preguiçoso), em outras palavras, é a técnica realizada para carregar os arquivos .js apenas quando forem necessários (o download será feito apenas uma vez).

Implementando o Code Splitting

Chega de teoria e vamos para a prática, hoje na aplicação para exemplo do post temos um componente referente as rotas da aplicação, o Routes:

import React from 'react'

import { Switch, Router, Route } from 'react-router'

import { history } from './history'

import HomeComponent from '../components/HomeComponent'

import AComponent from '../components/AComponent'
import BComponent from '../components/BComponent'
import CComponent from '../components/CComponent'

const Routes = () => (

    <Router history={ history }>
        <Switch>
            <Route component={ HomeComponent } exact path="/"/>
            <Route component={ AComponent } exact path="/a"/>
            <Route component={ BComponent } exact path="/b"/>
            <Route component={ CComponent } exact path="/c"/>
        </Switch>
    </Router>

)

export default Routes

Basicamente o mesmo está apenas importando os componentes e mapeando suas rotas. Por onde começamos a implementar o code splitting? O primeiro passo, será mudar a forma que estamos importando os componentes, não podemos mais importá-los diretamente através dos modules (módulos).

Precisamos começar a fazer uso da função lazy do React, para isso, devemos importá-la:

import React, { lazy } from 'react'

Mas, como utilizá-la? Agora nossos componentes devem ser variáveis que irão receber o componente através da função lazy:

const AComponent = lazy(() => import('../components/AComponent'))

O mesmo pode ser feito para todos os componentes dos quais queremos realizar o code splitting e lazy loading:

const HomeComponent = lazy(() => import('../components/HomeComponent'))

const AComponent = lazy(() => import('../components/AComponent'))
const BComponent = lazy(() => import('../components/BComponent'))
const CComponent = lazy(() => import('../components/CComponent'))

Mas, ao terminar a mudança e salvar, temos um problema:

Erro por falta do componente Suspense

Afinal, o que está acontecendo?

Conhecendo o componente Suspense

Bom, vamos entender um pouco melhor nosso problema, basicamente nosso erro é:

Um componente do React foi suspenso enquanto renderizava, porque não adicionamos o componente Suspense como pai de nosso componente que será carregado através de lazy loading.

Para resolver o problema simplesmente podemos encapsular nossas rotas dentro do componente Suspense:

import React, { Suspense, lazy } from 'react'

// códigos omitidos

const Routes = () => (

    <Router history={ history }>
        <Switch>
            <Suspense fallback={ <h1>Rendering...</h1> }>
                <Route component={ HomeComponent } exact path="/"/>
                <Route component={ AComponent } exact path="/a"/>
                <Route component={ BComponent } exact path="/b"/>
                <Route component={ CComponent } exact path="/c"/>
            </Suspense>
        </Switch>
    </Router>

)

// códigos omitidos

Agora, se tentarmos utilizar a aplicação novamente, temos o seguinte resultado:

Utilizando aplicação com lazy loading e code splitting

E podemos ver que nossos arquivos .js são baixados apenas quando necessários (quando acessarmos a rota de cada componente):

Baixando arquivos javascript quando necessários

Será que agora tudo está correto? Não, temos apenas um pequeno detalhe para corrigir, ao carregar a aplicação, se olharmos o console do navegador:

Mensagem de erro das rotas

Recebemos uma mensagem de erro, o que está acontecendo é que agora estamos passando um Object para a propriedade component do componente Route, porém, o mesmo espera que essa props seja uma função (function). Podemos resolver o problema de forma muito simples, apenas encapsulando nossos componentes com uma arrow function:

const Routes = () => (

    <Router history={ history }>
        <Switch>
            <Suspense fallback={ <h1>Rendering...</h1> }>
                <Route component={ () => <HomeComponent/> } exact path="/"/>
                <Route component={ () => <AComponent/> } exact path="/a"/>
                <Route component={ () => <BComponent/> } exact path="/b"/>
                <Route component={ () => <CComponent/> } exact path="/c"/>
            </Suspense>
        </Switch>
    </Router>

)

Agora, podemos utilizar e navegar pela aplicação sem erro nenhum em nosso console.

Mas, afinal, porque tivemos que utilizar o componente Suspense?

Saiba mais

Porque foi necessário o uso do Suspense? Ele foi criado pensando em melhorar a usabilidade de nossa aplicação, geralmente enquanto uma request está sendo feita ou um componente não está pronto para ser renderizado, nós mostramos um Spinner (ou qualquer outro nome que você use) para o usuário. Dessa maneira conseguimos dizer para ele:

Olha, calma, a aplicação está processando...

E quando de fato tudo termina e esta pronto, nosso componente é renderizado com suas informações.

Pensando em facilitar esse tipo de implementação para nós desenvolvedores, foi criado o componente Suspense que através do fallback faz exatamente isso, ele recebe um componente que será mostrado enquanto o download do arquivo .js está sendo feito e a renderização do nosso componente ainda não foi realizada.

Não percebemos muito isso, pois nossa internet é rápida, mas, graças as ferramentas do Google Chrome podemos simular uma internet 3G, assim conseguiremos ver o resultado de forma mais clara:

Usando aplicação com 3G

Para o exemplo do post apenas foi passado uma tag h1 dizendo que está sendo renderizado, mas, em um exemplo do mundo real, o mesmo seria um Spinner (como eu chamo esse tipo de componente).

Conclusão

Nesse post mostrei e expliquei um pouco sobre Code Splitting e Lazy Loading, duas técnicas para melhorar a performance e desempenho da nossa aplicação, pois, os arquivos .js do projeto serão menores e apenas serão baixados quando forem necessários. Com isso, também conseguimos economizar um pouco a internet do usuário (caso seja móvel).

Se você gostou do projeto, pode encontrá-lo em meu github.

Não deixe de comentar ou me procurar em redes sociais para falarmos um pouco sobre o assunto ou tirar alguma dúvida, se quiser, faça o cadastro na newsletter e recebe novidades por email assim que forem publicadas.

Até a próxima.

Matheus Castiglioni

Matheus Castiglioni

Apaixonado pelo mundo dos códigos e um eterno estudante, gosto de aprender e saber um pouco de tudo, aquela curiosidade de saber como tudo funciona, tento compartilhar o máximo de conhecimentos adquiridos e ajudar todos aqueles que sou capaz.

Post's Relacionados

Visualize outros post's relacionados ao mesmo assunto:

Limitando execução de função com takeUntil

Javascript
A maioria dos aplicativos feitos com a biblioteca React, terão seus arquivos empacotados, esse empacotamento pode ser feito com ferramentas de empacotação, por exemplo: Webpack ou Parcel (além de outras). Realizar o empacotamento de uma aplicação é...
Continuar lendo

Otimizando componentes do React com PureComponent

Javascript
A maioria dos aplicativos feitos com a biblioteca React, terão seus arquivos empacotados, esse empacotamento pode ser feito com ferramentas de empacotação, por exemplo: Webpack ou Parcel (além de outras). Realizar o empacotamento de uma aplicação é...
Continuar lendo

Programação Imperativa x Declarativa

Javascript
A maioria dos aplicativos feitos com a biblioteca React, terão seus arquivos empacotados, esse empacotamento pode ser feito com ferramentas de empacotação, por exemplo: Webpack ou Parcel (além de outras). Realizar o empacotamento de uma aplicação é...
Continuar lendo

Criando componente com Shadow DOM

Javascript
A maioria dos aplicativos feitos com a biblioteca React, terão seus arquivos empacotados, esse empacotamento pode ser feito com ferramentas de empacotação, por exemplo: Webpack ou Parcel (além de outras). Realizar o empacotamento de uma aplicação é...
Continuar lendo

Trabalhando com eventos no JavaScript

Javascript
A maioria dos aplicativos feitos com a biblioteca React, terão seus arquivos empacotados, esse empacotamento pode ser feito com ferramentas de empacotação, por exemplo: Webpack ou Parcel (além de outras). Realizar o empacotamento de uma aplicação é...
Continuar lendo