Como debugar rerenders desnecessários no React
56
0

Como debugar rerenders desnecessários no React

De forma resumida, os rerenders são re-renderizações que acontecem na sua aplicação devido principalmente ao descuido na hora da utilização do useEffect escutando dependências que podem forçar alterações que podem impactar todo o escopo da aplicação.

Matheus Cavallini
5 min
56
0
Email image

Olá caros amigos seguidores (e ainda não seguidores), hoje eu decidi voltar aqui pra falar sobre um assunto que envolve a minha carreira profissional, e pra começar a falar dessa área, eu resolvi trazer um tópico aqui na Pingback pra ajudar nossos amigos devs que também atuam na área de front-end desenvolvendo com React e que já devem ter passado muitas vezes por esse problema. Sem mais delongas, vamos direto ao assunto.

O que são os rerenders afinal?

De forma resumida, os rerenders são re-renderizações que acontecem na sua aplicação devido principalmente ao descuido na hora da utilização do useEffect escutando dependências que podem forçar alterações que podem impactar todo o escopo da aplicação. A alteração de estados (useState, setState) também impacta nesse sentido. E não só por esses dois motivos, mas também pela falta do uso de hooks do React, como por exemplo, o useCallback e useMemo (que vão aparecer em uma próxima publicação). Além dos hooks, existe outro método do React que é chamado de memo, mas nós vamos falar mais sobre cada um deles a seguir.

Aqui estão algumas razões que causam re-renderizações na aplicação:

  1. Hooks sofrendo alterações (ex: useState e setState sendo chamados)
  2. props input sofrendo alterações
  3. Um componente pai sofrendo rerender.

DevTools Profiler

O DevTools Profiler é um plugin que é utilizado diretamente no seu browser, e atualmente está disponível para Chrome e Firefox (há também uma versão Node). O link anterior vai direto pra documentação deste cara. Aproveito já para deixar o link dele para o Chrome.

O plugin disponibiliza a informação "Why did this render?", ou "Porquê isso renderizou?"
O plugin disponibiliza a informação "Why did this render?", ou "Porquê isso renderizou?"

Esta ferramenta é simples de ser utilizada, e para enxergar a informação acima, basta você abrir as configurações do plugin e marcar a opção "Record why each component rendered while profiling". Após isso, você pode executar a gravação do Profiler e efetuar ações na sua tela para poder identificar onde estão acontecendo as renderizações dentro do escopo onde você navegou (uma mão na roda, não?). Dessa forma, você não precisa encher o seu código de console.logs pra identificar o porquê que um componente seu renderizou de forma desnecessária.

Após finalizar a gravação do Profiler, você consegue interagir com cada renderização de componentes que aconteceram enquanto você estava gravando. De todas as ferramentas de debugs disponíveis para React, esta é com certeza a mais rápida e fácil de se trabalhar com os resultados, mas como nem tudo são flores, o plugin ainda não disponibiliza a inspeção dos valores das propriedades que foram alteradas. Para ter essa informação, você pode utilizar o React.memo.

React.memo

O React v.16.6.0 nos deu um novo método que pode ser usado tanto com React funcional, como com React baseado em classes para termos mais controle sobre os rerenders, ele é similar ao shouldComponentUpdate. Ele não é só uma boa ferramenta para controlar os rerenders, mas também pode ser útil para descobrir onde eles estão acontecendo.

O segredo para esse metódo funcionar a nosso favor, é utilizar o segundo parâmetro dele, que basicamente é um "isEqual", ou seja, uma validação boleana para dizer ao React se o componente continua sendo o mesmo da penúltima renderização. Ficou confuso? Então imagine que você tem um prevState e um nextState, e dentro deles você consegue basicamente comparar todas as props que você tem dentro do componente, e dizer pro React se o seu componente realmente precisa ser renderizado novamente.

import React, { memo } from "react";
const MyComponent = ({ name }) => {
return <div>{`Hi, my name is ${name}!`}</div>;
};
const isEqual = (prevState, nextState) => {
console.log('Previous State', prevState);
console.log('Next State', nextState);
if (prevState.name !== nextState.name) {
return false;
}
return true;
};
export default memo(MyComponent, isEqual);

Se você ficou interessado e quiser aprender mais sobre este método, aqui está a documentação para você poder ir mais a fundo na sua leitura e utilizar ele a seu favor.

O uso do React.memo não precisa ser feito necessáriamente com a utilização de uma função (que é o segundo parâmetro dele) para validar manualmente se o componente precisa ser re-renderizado, você pode também apenas envolver o seu componente, e deixar que o React faça o resto do trabalho por você. Ele é um HOC (High-Order Component), e isso significa basicamente que é uma função que "pega" um componente e retorna um novo componente. Antes de você sair codando com ele, devo antecipar, UTILIZE ELE COM CAUTELA, se não você vai terminar "curando" cada componente com React.memo.

React.Profiler

Então agora vamos falar do React.Profiler, esse que faz parte da API do React, que disponibiliza para os desenvolvedores dados adicionais que podem ser utilizados para debugar problemas de performance na renderização.

Basicamente, você precisa somente englobar o componente que você quer debugar com o <Profiler>, e ele espera somente 2 props:

  1. id - um id único para identificar a seção que vai ser escutada.
  2. onRender - uma função callback que vai ser chamada a cada render. Aqui está a documentação para você entender quais são esses parâmetros.

Para facilitar a sua vida dev, eu desenvolvi um componente que recebe um children e um id e já trata os dados prontinhos para você debugar. Fique a vontade para usá-lo:

import React, { Profiler } from 'react';
import PropTypes from 'prop-types';
const ProfilerForDevs = props => (
<Profiler
id={props.id}
onRender={(...args) => {
console.log({
Id: args[0],
Phase: args[1],
ActualDuration: args[2],
BaseDuration: args[3],
StartTime: args[4],
CommitTime: args[5],
Interactions: args[6]
});
}}
>
{props.children}
</Profiler>
);
ProfilerForDevs.propTypes = {
id: PropTypes.string.isRequired,
children: PropTypes.node.isRequired
};
export default ProfilerForDevs;

Deixo aqui algumas dicas de como ele pode ser útil:

  1. Tenha certeza de que nunca um componente reverta para a phase mount após a renderização inicial, sempre deve ser updated.
  2. A actualDuration deve diminuir depois da renderização inicial. Se continuar a mesma ou acima, você aparentemente não está renderizando o seu children de maneira eficiente.
  3. Para melhor entendimento de qual ação do usuário que está causando rerender, você pode rastrear os timestamps (datas em milissegundos) de múltiplas ações e ver qual está relacionada com o startTime.
  4. O baseDuration vai lhe dizer o pior cenário possível quando um componente for re-renderizado. Componentes com o maior baseDuration são os que você precisa ter atenção redobrada quando estiver otimizando os rerenders.

Então é isso caros amigos leitores! Espero que tenham gostado, já peço que deixem seu ping no post e utilizem a área de comentários para deixar dicas/sugestões/críticas. Basta clicar no balãozinho de comentários que está aparecendo na sua tela ao lado esquerdo (desktop), ou no bottom do seu navegador (mobile). E antes que eu esqueça, esse texto foi baseado em um artigo escrito por Bryce Dooley, com o acréscimo de informações feitas por minha parte. Até a próxima!

Seguir meu Canal