Code splitting e lazy loading no React

A maioria dos aplicativos feitos com a biblioteca React, terão seus arquivos empacotados, esse empacotamento pode ser feito com...

Data de publicação: 14/01/2019
Code Splitting-e Lazy Loading no React

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:
Exemplo do projeto
 
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:
5c00749f72edd_bg
 
Mas, será que podemos melhorar nossa aplicação?
5c007596cdd9a_bg
 
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:
5c007a0bdba7b_bg
 
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:
5c007dbce65a2_bg
 
E podemos ver que nossos arquivos .js são baixados apenas quando necessários (quando acessarmos a rota de cada componente):
5c007dbce750d_bg
 
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:
5c0085ff5bc04_bg
 
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:
5c00803772b9c_bg
 
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.
Até a próxima.

Posts relacionados

  1. Sobre a Dextra

    Somos especialistas em desenvolvimento de software sob medida para negócios digitais. Pioneiros na adoção de metodologias de gestão ágil, combinamos processos de design, UX, novas tecnologias e visão de negócio, desenvolvendo soluções que criam oportunidades para nossos clientes.

  2. Categorias

Scroll to top