- Por que o React re-renderiza?
- Verdade 1 sobre re-renderização
- Verdade 2 sobre re-renderização
- Mal-entendido 1: o app inteiro re-renderiza sempre que ocorre uma mudança de estado
- Mal-entendido 2: um componente é re-renderizado porque uma prop que ele recebe mudou
- Demarcando componentes puros
- Memoização
- Bibliografia
Existem alguns mal-entendidos (misconceptions) a respeito de por que o React... reage. É uma característica fundamental que confesso que passei anos achando que compreendia por esse conceito não ser crucial no dia a dia.
É curioso, entretanto, imaginar que um dev com anos de experiência em front-end, especificamente dentro do ecossistema do React, não sabia responder de bate-pronto quais são os eventos que ativam as re-renderizações do React e o que acontece depois que a re-renderização é disparada. Esse dev era eu — e foi por isso que criei esta talk. Se você tem a mesma dúvida que eu tinha, vou tentar te ajudar. É bem simples.
Se para pelo menos uma pessoa presente esse conhecimento repassado não for óbvio, a talk já terá cumprido seu objetivo.
- Toda re-renderização é disparada por uma mudança de estado.
Ué, mas a re-renderização não acontece quando uma prop muda?
-
Quando um componente re-renderiza, ele necessariamente re-renderiza todos os seus descendentes. Não apenas filhos e netos, toda a árvore abaixo dele.
-
Então, se toda re-renderização é disparada por uma mudança, todo componente que possui uma variável de estado definida se torna um "topo de árvore" de re-renderizações.
-
Toda re-renderização é disparada por uma mudança de estado. O entendimento completo sobre o life cycle do React decorre deste axioma
-
Uma dúvida relacionada a disso é: mas quando props mudam, o React também dispara uma re-renderização. Certo?
-
Existem duas respostas:
-
uma prop re-renderizada, esteja onde estiver, re-renderiza o componente no qual ela nasce
-
um componente re-renderizado, automaticamente re-renderiza todos os seus descendentes, até o final da árvore
- Como o papel do React é… reagir a mudanças de estado, ele utiliza essa estratégia de top-down absoluto de re-renderização para manter o virtual DOM sempre sincronizado na tela. Veremos adiante que o próprio React oferece uma ferramenta para interromper essa cadeia em qualquer ponto da árvore.
Se um estado criado dentro de um componente é passado como prop e é atualizado em um dos descendentes, o componente-pai irá disparar uma re-renderização. Top-down. Todos os descendentes do pai são re-renderizados.
Então, para reforçar, um componente não re-renderiza porque alguma das props que ele recebe foi alterada, e sim porque ele é filho de um componente que re-renderizou por conta de uma atualização de estado.
Componentes não re-renderizam por causa das props.
É possível, no entanto, criar exceções para esse comportamento-padrão.
Antes de avisar ao React que um dado componente é puro, primeiro é necessário entender o que é uma função pura, já que para o React componentes são funções.
O conceito de função pura é emprestado da programação funcional e significa que uma função sempre deve retornar valores iguais para parâmetros iguais, caso contrário há algum side effect atuando sobre ela. Traduzindo para o front-end, um componente puro é aquele cuja UI se comporta da mesma maneira sempre receber as mesmas props. Não existem efeitos colaterais que possam afetar seu comportamento de maneira inesperada, como por exemplo o fuso-horário, a previsão do tempo etc. Uma função que não recebe props e também possui esse comportamento previsível também é considerada pura.
Ou seja, se a UI de um componente não precisa ser atualizada por mudanças de estados occorridas em seus ascendentes, então esse componente é puro e não precisa ser re-renderizado. É aqui que entra o conceito de memoização.
A memoização torna o componente capaz de ignorar re-renderizações não endereçadas a ele — visto que um componente é re-renderizado apenas quando um estado pertencente a ele é atualizado ou quando ele é descendente de algum componente que disparou um re-render. A memoização é feita através da função React.memo().
import { memo } from "react";
function MyPureComponent() {
return <p>I am a pure component!</p>;
}
export default memo(MyPureComponent());
Como foi dito anteriormente, o comportamento padrão é esse porque a tarefa principal do React é manter a UI atualizada a partir de em mudanças de estado dos componentes.
Obs.: como cada componente que possui um estado definido é uma nova instância de re-renderizações na árvore, o memo()
não impede que o componente em si dispare atualizações para seus descendentes (filhos, netos etc.).
Nós desenvolvedores tendemos a acreditar que as re-renderizações são mais custosas do que realmente são. Quando descobrem que o memo()
impede que componentes sejam re-renderizados, algumas pessoas podem passar a superutilizá-lo.
Na prática, o que acontece é que o React prefere pecar pelo excesso do que porventura deixar um componente desatualizado. O uso do React.memo()
, então, pode ser descartado em componentes:
- que possuem poucos descendentes;
- que não fazem muitos cálculos antes de renderizar HTML;
- que não recebem muitas props — e, portanto, não precisam checar seus estados antes de renderizar seus dados ou passá-los adiante.
- Este README texto foi inspirado no Why React Re-Renders do Josh Comeau num nível tão suspeito que beira o plágio. Leia o texto dele para mais exemplos e mais cenários.