Início Artigos Como reduzir em 60% o tempo de carregamento das aplicações com React
Como reduzir em 60% o tempo de carregamento das aplicações com React
A primeira versão do aplicativo web foi criada em 2015 com o Vanilla JavaScript, patchwork de utilitários e bibliotecas de UI. Éramos uma equipe pequena e manipular o DOM ou gerenciar o estado em funções individuais, foi suficiente para concluir o trabalho.
Dois anos depois, queríamos adicionar uma camada de colaboração e workflow (pense no Google Docs + Trello para vídeo). A equipe de desenvolvimento também cresceu, precisávamos de uma maneira melhor de adicionar recursos, garantir a testabilidade e evitar a imprevisibilidade de uma grande base de código mal estruturada. Parecia a hora certa de entrar, cuidadosamente, no ecossistema React.
Mudando para o React
O React foi uma escolha fácil, pois vinha com um fluxo previsível de atualizações para UI. O React não define bem o gerenciamento do estado da aplicação ou como lidar com o roteamento, por isso decidimos usar o Redux para o gerenciamento do estado e o React Router para o roteamento do lado do cliente.
Criar um aplicativo com o React é tranquilo, pois fornece uma ótima maneira de estruturar a interface do usuário em componentes, facilitando a visualização de como a interface do usuário funciona, inclusive fornece uma maneira mais fácil de reutilizar os componentes. Não tivemos boas experiências com o Redux no começo, mas nos salvou de muitas dores de cabeça no longo prazo.
Atingindo o limite da performance
O React lida com as atualizações da interface do usuário com eficiência, mas isso não o torna a aplicação web mais rápida. Precisávamos saber como funcionava, como o controle fluía dentro dos componentes e como atualizava o DOM. À medida que o aplicativo avançava, percebemos algumas desvantagens na configuração. Embora soubéssemos como o React funcionava e como o Redux gerenciava o estado, a aplicação tinha aumentado de tamanho. Começamos a notar falhas no carregamento. Estava na hora de reduzir a ignorância técnica e fazer melhorias de desempenho!
Abaixo estão algumas coisas que fizemos e o que esperamos fazer no futuro para melhorar o desempenho da aplicação.
Otimização de performance do React
Medindo a performance com o Chrome DevTools.
O primeiro passo da otimização é a medição. Somente após identificar os gargalos, podemos eliminá-los. O Chrome DevTools oferece poderosas medições de desempenho para aplicações React.
O Chrome Devtools mostra quais componentes são renderizados
Linhas vermelhas mostram picos no FPS
Atualização para webpack 4
Por um ano, usamos o Webpack 3 sem preocupação. Funcionou muito bem e não tivemos nenhuma queixa. O Webpack 4 não influencia diretamente no desempenho, mas atualizamos para facilitar a divisão de código. Infelizmente, o Webpack 4 mudou muitas APIs, tornando a transição um pouco mais desafiadora do que havíamos esperado. No entanto, essa mudança nos permitiu adicionar alguns detalhes ao workflow de desenvolvimento, como passar do CommonChunksPlugin descontinuado para o SplitChunksPlugin além de configurar a divisão de código.
Divisão de código
A configuração inicial não incluiu a divisão de código por padrão. Isso não era necessário quando o aplicativo era relativamente pequeno, com poucos componentes e lógica comercial simples. Com ritmo feroz de desenvolvimento de produtos com flags de recursos e testes A/B, muito mais componentes foram adicionados e o fluxo lógico ficou complicado. Colocar tudo isso em um único pacote estava travando o desempenho da aplicação.
Por que isso é um problema? Um pacote com muito mais código do que o necessário, será sempre maior do que deveria. O navegador analisará todo o código, independentemente de qual seção será carregada. Carregar um código extra significa downloads mais lentos e mais trabalho para o navegador, levando a tempos de resposta longos. Isso é muito mais perceptível em conexões lentas e hardware com baixo desempenho.
Para resolver esses problemas, configuramos a divisão de código baseada em rota usando o pacote npm react-loadable. Em seguida, passamos para o React.lazy e o Suspense apesar de não suportar a renderização do servidor no momento. Trocamos porque agora havia uma maneira integrada de lidar com importações dinâmicas.
Configurar a divisão de código com SplitChunksPlugin foi fácil. Foram necessárias algumas iterações para corrigir o número de blocos. O tamanho de pacote inicial para toda a aplicação era de 7 MB. Após a divisão do código, os pacotes configuráveis iniciais tinham um tamanho combinado de 230kb, uma enorme redução de 97% no tamanho dos pacotes da aplicação!
Otimização de Long list
Existem alguns locais nas aplicações em que utilizamos Long lists, lista de fontes, faixas de música, imagens e clipes, modelos, gráficos em movimento, entre outros. Algumas dessas listas fazem chamadas HTTP em segundo plano e adicionam novos itens quando finalizadas, por exemplo, um scroll infinito. A maioria das listas na aplicação não fez isso e renderizou um grande número de itens.
A renderização desses itens estava sobrecarregando o navegador e vimos muitas falhas no carregamento. Para corrigir isso, usamos o módulo npm react-window, que fornece um componente de ordem superior que controla a renderização de listas longas. Não processa itens que não são imediatamente visíveis. Suporta carregamento lento, propriedades personalizadas e manipuladores de eventos. Isso nos deu renderização de lista a 60fps.
Reduzindo renderizações desnecessárias
As atualizações de estado geralmente acontecem em aplicações grandes e complexas. Algumas são assíncronas, como chamadas HTTP em segundo plano, e outras ocorrem após a aplicação da lógica de negócios. É bastante comum ver componentes renderizando várias vezes antes de qualquer interação do usuário. Cabe aos desenvolvedores detectar essas renderizações desnecessárias e evitá-las quando não forem necessárias.
Renderizações desnecessárias são renderizações de componentes que não precisam deste processo. É um problema em especial se as renderizações acontecerem nos componentes pai. Isso ocorre porque o React renderiza novamente todos os componentes em uma árvore quando o pai é renderizado. Isso causa processamento desnecessário.
Encontrando componentes que fazem renderizações desnecessárias
A extensão React developer tool possui a opção "Highlight Updates" que usamos para encontrar os componentes que renderizam sem necessidade. Depois de identificar os componentes, corrigimos de duas maneiras diferentes.
- Usando
shouldComponentUpdatepara informar ao React que o resultado do componente não é afetado pela alteração de estado para controlar quando um componente deve ser renderizado novamente; - Reorganizando a estrutura dos componentes e como as mudanças de estado acontecem. Esse é um processo mais envolvido e demoramos mais para identificar os componentes que são renderizados desnecessariamente.
Próximos passos
Temos vários objetivos para melhorar ainda mais o desempenho das aplicações num futuro próximo.
Utilizar React.PureComponent
O React.PureComponent é semelhante ao React.Component. A principal diferença é que o PureComponent implementa o shouldComponentUpdate com uma comparação superficial. Diz-se que isso melhora o desempenho, mas vem com algumas consequências indesejadas.
Análise de compilação
Visualizar compilações é uma ótima maneira de entender o que ocorre nos pacotes. O analisador de pacotes Webpack mostra a separação de todos os pacotes gerados para a aplicação em uma página da web interativa. O Webpack também permite definir orçamentos de desempenho para que possamos saber quando ativos e pacotes configuráveis excedem os limites de arquivo. Também podemos identificar e remover códigos duplicados nos pacotes com o plugin third party Webpack.
Tree shaking melhorado
O Tree shaking é o processo de remoção do código não utilizado ou inoperante dos pacotes configuráveis. Isso é importante principalmente ao utilizar as bibliotecas de utilitários como o Lodash, onde não precisamos de todos os recursos da biblioteca importada.
Service Workers e o PWA
Os web apps progressivos são confiáveis, rápidos e envolventes. São carregados rapidamente, mesmo com internet discada, e possuem alto desempenho. Foram criados usando as mais recentes tecnologias web e oferecem experiências semelhantes a aplicativos.
Prefetching
O Prefetching é uma maneira de informar ao navegador sobre os recursos que provavelmente serão solicitados e buscá-los antes que sejam necessários. Isso é feito assim que os recursos necessários para a página atual são carregados e quando o navegador está ocioso. Para aplicativos renderizados no servidor, a API Push HTTP/2 possui suporte no Node.js.
Tornando aplicações React rápidas novamente
Após seguir as etapas deste artigo, conseguimos melhorar o desempenho geral das aplicações em mais de 60%, reduzindo o tamanho do pacote inicial de 7 MB para 230 KB, o tempo de carregamento total de 47 segundos para 19 segundos (4G lento) e o tempo de interação de 23 segundos a 15 segundos (4G lento), melhorando bastante a experiência do usuário para as aplicações.

Melhorias gerais pós otimizações
O ecossistema React é vasto e poderoso. Podemos aproveitar as inúmeras ferramentas disponíveis para criar aplicações grandes e complexas. Acima, estão apenas algumas técnicas que podem tornar as aplicações rápidas e mais suaves. Definitivamente, o esforço adicional vale os benefícios de obter uma base de código de maior desempenho e mais sustentável.
Sobre o autor
Ilango Rajagopal é engenheiro de software sênior da Rocketium com mais de 4 anos de experiência na criação de aplicações web escaláveis. É talentoso no desenvolvimento de excelentes UI designs e gosta de descobrir novos sites. Em seu tempo livre, gosta de aprender sobre novas tecnologias, jogar e ler livros.


