Otimizar o desempenho do aplicativo é fundamental para desenvolvedores que se preocupam em manter a experiência do usuário positiva para mantê-los no aplicativo e engajados.

De acordo com a pesquisa da Akamai, um segundo atraso no tempo de carregamento pode causar uma redução de 7% nas conversões, tornando imperativo que os desenvolvedores criem aplicativos com desempenho otimizado.

Para aplicativos criados com React, temos a garantia de uma IU muito rápida por padrão. No entanto, conforme um aplicativo cresce, os desenvolvedores podem encontrar alguns problemas de desempenho.

Neste guia, discutiremos cinco maneiras importantes de otimizar o desempenho de um aplicativo React, incluindo técnicas de pré-otimização. Isso inclui:

Manter o estado do componente local quando necessário Memorizar componentes React para evitar reenvios desnecessários Divisão de código no React usando importação dinâmica () Criação de janelas ou virtualização de lista no React Imagens de carregamento lento no React

Técnicas de pré-otimização do React

Antes de otimizar um Aplicativo React, devemos entender como o React atualiza sua IU e como medir o desempenho de um aplicativo ce. Isso facilita a solução de quaisquer problemas de desempenho do React.

Vamos começar revisando como a IU do React é atualizada.

Entendendo como o React atualiza sua IU

Quando nós criar um componente renderizado, o React cria um DOM virtual para sua árvore de elementos no componente. Agora, sempre que o estado do componente muda, o React recria a árvore virtual do DOM e compara o resultado com a renderização anterior.

Ele então atualiza apenas o elemento alterado no DOM real. Esse processo é chamado de diffing.

O React usa o conceito de um DOM virtual para minimizar o custo de desempenho de renderizar novamente uma página da web porque o DOM real é caro de manipular.

Isso é ótimo porque ele acelera o tempo de renderização da IU. No entanto, esse conceito também pode tornar um aplicativo complexo se ele não for gerenciado muito bem.

O que podemos deduzir aqui é que uma mudança de estado em um componente React causa um novo renderizador. Da mesma forma, quando o estado passa para um componente filho como um suporte, ele é processado novamente no filho e assim por diante, o que é bom porque o React deve atualizar a IU.

O problema surge quando os componentes filhos não são afetados pela mudança de estado. Em outras palavras, eles não recebem nenhum prop do componente pai.

Mesmo assim, o React renderiza novamente esses componentes filhos. Portanto, contanto que o componente pai seja processado novamente, todos os seus componentes filhos serão processados ​​novamente, independentemente de um adereço passar para eles ou não; este é o comportamento padrão do React.

Vamos demonstrar rapidamente esse conceito. Aqui, temos um componente App contendo um estado e um componente filho:

import {useState} from”react”; função padrão de exportação App () {const [input, setInput]=useState (“”); return (

); } function ChildComponent () {console.log (“componente filho está sendo renderizado”); return

Este é um componente filho.

; };

Sempre que o estado do componente App é atualizado, o ChildComponent é processado novamente, mesmo quando não é diretamente afetado pela mudança de estado.

Abra o console nesta demonstração CodeSandbox e escreva algo no campo de entrada. Veremos que, para cada pressionamento de tecla, o ChildComponent é processado novamente.

Na maioria dos casos, esse novo processamento não deve causar problemas de desempenho e não devemos notar qualquer atraso em nosso aplicativo. No entanto, se o componente não afetado renderizar um cálculo caro e notarmos problemas de desempenho, devemos otimizar!

Isso nos leva à segunda técnica de pré-otimização.

Criação de perfil do aplicativo React para entender onde estão os gargalos

O React nos permite medir o desempenho de nossos aplicativos usando o Profiler no React DevTools . Lá, podemos reunir informações de desempenho sempre que nosso aplicativo é renderizado.

O criador de perfil registra quanto tempo leva para um componente renderizar, por que um componente está renderizando e muito mais. A partir daí, podemos investigar o componente afetado e fornecer a otimização necessária.

Para usar o Profiler, devemos instalar o React DevTools para o navegador de sua escolha. Se você ainda não o instalou, vá para a página de extensão e instale-o (escolha Chrome aqui ou para Firefox aqui ).

Agora, devemos ver a guia Profiler ao trabalhar em um projeto React.

De volta ao nosso código, se criarmos o perfil do aplicativo, veremos o seguinte comportamento:

O criador de perfil DevTools destaca cada componente renderizado enquanto o campo de texto de entrada é atualizado e recebemos todos os detalhes dos componentes renderizados. No gráfico abaixo, podemos ver quanto tempo levou para renderizar os componentes e por que o componente do aplicativo está renderizando.

Da mesma forma, a imagem abaixo mostra que o componente filho está sendo renderizado porque o pai componente renderizado.

Isso pode impactar o desempenho do aplicativo se tivermos uma operação em um componente filho que leva tempo para ser computada. Isso nos leva às nossas técnicas de otimização.

Reaja as técnicas de otimização de desempenho

1. Manter o estado do componente local quando necessário

Aprendemos que uma atualização de estado em um componente pai renderiza novamente o pai e seus componentes filhos.

Portanto, para garantir que a nova renderização de um componente só aconteça quando necessário, podemos extrair a parte do código que se preocupa com o estado do componente, tornando-o local para aquela parte do código.

Refatorando nosso código anterior, temos o seguinte:

import { useState} de”react”; função padrão de exportação App () {return (

); } function FormInput () {const [input, setInput]=useState (“”); return (

); } function ChildComponent () {console.log (“componente filho está sendo renderizado”); return

Este é um componente filho.

; }

Isso garante que apenas o componente que se preocupa com o estado seja processado. Em nosso código, apenas o campo de entrada se preocupa com o estado. Portanto, extraímos esse estado e a entrada para um componente FormInput, tornando-o irmão do ChildComponent.

Isso significa que, quando o estado muda no componente FormInput, apenas o componente é processado novamente.

Se testarmos o aplicativo mais uma vez em nossa demonstração CodeSandbox , o ChildComponent não é mais processado novamente a cada pressionamento de tecla.

Mas, às vezes, não podemos evitar ter um estado em um componente global ao passá-lo para os componentes filho como um suporte. Nesse caso, vamos aprender como evitar renderizar novamente os componentes filhos não afetados.

2. Memorizando os componentes do React para evitar rerenders desnecessários

Ao contrário da técnica de desempenho anterior, onde refatorar nosso código nos dá um aumento de desempenho, aqui trocamos espaço de memória por tempo. Portanto, devemos memorizar um componente apenas quando necessário.

Memoização é uma estratégia de otimização que armazena em cache uma operação renderizada por componente, salva o resultado na memória e retorna o resultado armazenado em cache para a mesma entrada.

Em essência, se um componente filho recebe um prop, um componente memoized superficialmente compara o prop por padrão e pula a renderização do componente filho se o prop não mudou:

import {useState} from”react”; função padrão de exportação App () {const [input, setInput]=useState (“”); const [contagem, setCount]=useState (0); return (

); } function ChildComponent ({count}) {console.log (“componente filho está sendo renderizado”); return (

Este é um componente filho.

Contagem: {count}

); }

Ao atualizar o campo de entrada, o botão de contagem renderiza novamente o App e ChildComponent .

Em vez disso, o ChildComponent só deve renderizar novamente ao clicar no botão de contagem porque ele deve atualizar a IU. Neste caso, podemos memorizar o ChildComponent.

Usando React.memo ()

Envolvendo um componente puramente funcional em React.memo, queremos renderizar novamente o componente apenas se for alterações de prop:

import React, {useState} de”react”;//… const ChildComponent=React.memo (function ChildComponent ({count}) {console.log (“componente filho está sendo renderizado”); return (

Este é um componente filho.

Contagem: {contagem}

);});

Se a propriedade de contagem nunca mudar, o React irá pular a renderização do ChildComponent e reutilizar o resultado renderizado anterior. Portanto, melhorando o desempenho do aplicativo.

Você pode tentar isso no tutorial sobre CodeSandbox .

React.memo () funciona muito bem quando passamos valores primitivos, como um número em nosso exemplo. E, se você estiver familiarizado com igualdade referencial , os valores primitivos são sempre referencialmente igual e retorna verdadeiro se os valores nunca mudarem.

Valores não primitivos como objeto, que incluem matrizes e funções, sempre retornam falso entre os re-renderizadores. Isso ocorre porque quando o componente é renderizado novamente, o objeto está sendo redefinido.

Quando passamos o objeto, array ou função como um suporte, o componente memoized sempre é redefinido. Aqui, estamos passando uma função para o componente filho :

import React, {useState} de”react”; função padrão de exportação App () {//… const incrementCount=()=> setCount (contagem + 1); return (

{/*… */}

); } const ChildComponent=React.memo (function ChildComponent ({count, onClick}) {console.log (“child component is rendering”); return (

{/*… */} {/*… */}

);});

Este código se concentra na função incrementCount passando para o ChildComponent. Quando o componente App é renderizado novamente, mesmo quando o botão de contagem não é clicado, a função é redefinida, fazendo com que o componente Child também seja renderizado novamente.

Para evitar que a função sempre seja redefinida, usaremos um gancho useCallback que retorna um memoized versão do retorno de chamada entre renderizações.

Usando o gancho useCallback

Com o gancho useCallback, a função incrementCount redefine apenas quando o array de dependência de contagem muda:

const incrementCount=React.useCallback (()=> setCount (contagem + 1), [contagem]);

Você pode tentar por si mesmo no CodeSandbox .

Usando o gancho useMemo

Quando o prop que passamos para um componente filho é um array ou objeto, podemos usar um gancho useMemo para memorizar o valor entre renderizações. Isso nos permite evitar recomputar o mesmo valor em um componente.

Semelhante a useCallback, o gancho useMemo também espera uma função e uma matriz de dependências:

const memoizedValue=React.useMemo (()=> {//retorna cálculo caro}, []);

3. Divisão de código no React usando import () dinâmico

A divisão de código é outra técnica de otimização importante para um aplicativo React.

Por padrão, quando um aplicativo React é renderizado em um navegador, um O arquivo “empacotar” contendo todo o código do aplicativo é carregado e servido aos usuários de uma só vez. Esse arquivo é gerado pela fusão de todos os arquivos de código necessários para fazer um aplicativo da Web funcionar.

A ideia de empacotamento é útil porque reduz o número de solicitações HTTP que uma página pode manipular. Porém, à medida que um aplicativo cresce, os tamanhos dos arquivos aumentam, aumentando assim o arquivo do pacote.

A certa altura, esse aumento contínuo do arquivo retarda o carregamento da página inicial, reduzindo a satisfação do usuário.

Com a divisão de código, o React nos permite dividir um grande arquivo de pacote em vários pedaços usando import () dinâmico seguido de carregamento lento desses pedaços sob demanda usando o React.lazy. Essa estratégia melhora muito o desempenho da página de um aplicativo React complexo.

Para implementar a divisão de código, transformamos uma importação React normal assim:

import Home from”./components/Home”; importar Sobre de”./components/Sobre”;

E então em algo assim:

const Home=React.lazy (()=> import (“./components/Home”)); const Sobre=React.lazy (()=> importar (“./componentes/Sobre”));

Esta sintaxe diz ao React para carregar cada componente dinamicamente. Portanto, quando um usuário segue um link para a página inicial, por exemplo, o React baixa apenas o arquivo da página solicitada, em vez de carregar um grande pacote de arquivo para todo o aplicativo.

Após a importação, devemos renderizar os componentes lazy dentro de um componente Suspense como:

Carregando página…

}>

O Suspense nos permite exibir um texto ou indicador de carregamento como um substituto enquanto o React espera para renderizar o preguiçoso componente na IU.

Você pode experimentar por si mesmo em o tutorial do CodeSandbox .

4. Virtualização de janelas ou lista no React

Imagine que temos um aplicativo onde renderizamos várias linhas de itens em uma página. Independentemente de qualquer um dos itens serem exibidos ou não na janela de visualização do navegador, eles são renderizados no DOM e podem afetar o desempenho de nosso aplicativo.

Com o conceito de janelamento, podemos renderizar para o DOM apenas a parte visível para o usuário. Então, ao rolar, os itens restantes da lista são renderizados enquanto substituem os itens que saem da janela de exibição. Essa técnica pode melhorar muito o desempenho de renderização de uma grande lista.

Ambos react-window e react-virtualized são duas janelas populares bibliotecas que podem implementar este conceito.

5. Imagens de carregamento lento no React

Para otimizar um aplicativo que consiste em várias imagens, podemos evitar renderizar todas as imagens de uma vez para melhorar o tempo de carregamento da página. Com o carregamento lento, podemos esperar até que cada uma das imagens esteja prestes a aparecer na janela de visualização antes de renderizá-las no DOM.

Semelhante ao conceito de janelamento mencionado acima, as imagens de carregamento lento evitam a criação de nós DOM desnecessários, aumentando o desempenho de nosso aplicativo React.

react-lazyload e react-lazy-load-image-component são bibliotecas populares de carregamento lento que podem ser usadas em projetos React.

Conclusão

Para iniciar um processo de otimização, devemos primeiro encontrar um problema de desempenho em nosso aplicativo para corrigir. Neste guia, explicamos como medir o desempenho de um aplicativo React e como otimizar o desempenho para uma melhor experiência do usuário.

Se você gostar deste guia, certifique-se de compartilhá-lo na web. Além disso, deixe-me saber qual das técnicas mais interessa a você.