Como React renderiza
"Renderizar" é uma das palavras mais usadas em React e uma das menos explicadas. Entender o que realmente acontece entre uma atualização de estado e os pixels mudando na tela esclarece bastante coisa: por que componentes executam quando executam, por que a maioria das re-renderizações não custa praticamente nada, e onde de verdade o trabalho de performance deveria começar. Este capítulo constrói esse modelo mental. Os dois capítulos que vêm depois, Memoização e Code splitting, colocam isso em prática.
Uma renderização é uma chamada de função
Quando React "renderiza" um componente, ele chama sua função de componente:
function Counter() {
const [count, setCount] = useState(0)
console.log('Counter renderizou') // executa toda vez que React chama essa função
return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}É só isso. A função executa de cima a baixo, qualquer cálculo dentro dela executa de novo, e ela retorna JSX descrevendo como a interface deveria parecer agora. Nada disso envolve o DOM do navegador ainda. Uma renderização produz uma descrição; ela não toca na página.
Essa distinção importa porque essas duas coisas têm custos bem diferentes. Chamar uma função JavaScript e construir uma descrição é rápido. Mudar o DOM de verdade, o que dispara trabalho de layout e pintura no navegador, é a parte cara. Todo o design do React se apoia em fazer a coisa barata livremente e a coisa cara o mínimo possível.
Os três passos
Uma única "re-renderização" passa por três passos. O gatilho geralmente é uma atualização de estado, mas a montagem inicial, um componente pai renderizando acima do componente, e um novo valor de contexto também executam a mesma maquinaria.
- Renderizar. React chama a função do componente cujo estado mudou, e então, recursivamente, todos os componentes que ela retorna. O resultado é um novo virtual DOM: uma descrição leve em memória daquela parte da interface.
- Reconciliação. React executa um algoritmo de diff comparando o novo virtual DOM contra o anterior, descobrindo exatamente o que mudou entre eles.
- Commit. As diferenças, e apenas as diferenças, são aplicadas ao DOM de verdade. Essa é a parte que o usuário consegue ver.
O curso oferece uma analogia que vale manter. Um arquiteto atualizando um prédio primeiro redesenha a planta (renderizar), depois lista quais mudanças a nova planta realmente exige (reconciliação), e só aí o time de construção toca no prédio (commit). Redesenhar uma planta é rápido. Construção é onde tempo e dinheiro vão, então o time faz o mínimo de trabalho que a lista permite.
Renderizar e reconciliação permanecem rápidos, e o commit fica limitado ao que o diff encontrou, é por isso que React se sente rápido por padrão. Quando algo fica lento, o Profiler te diz qual passo culpar em vez de deixar você adivinhar.
Todo clique no botão Counter executa os três: o log dispara, React faz o diff do resultado, e o commit atualiza o nó de texto do botão e mais nada ao seu redor.
Renderizar é recursivo, e isso normalmente é ok
Quando um componente renderiza, React renderiza tudo que ele retorna, depois tudo que esses componentes retornam, descendo cada branch até o fim antes de passar para o próximo. Uma mudança de estado em um componente pai portanto re-renderiza todos os seus descendentes por padrão, independente de receberem ou não o estado mudado como props.
Isso soa como desperdício até você separar os passos de novo. Essas renderizações filhas são chamadas de função produzindo descrições. Durante a reconciliação, React não encontra atributos mudados em nenhuma delas, então o commit não tem trabalho de DOM a fazer para aquela subárvore. O trabalho visível de DOM do usuário fica limitado ao que realmente mudou. Re-renderizar uma subárvore que não faz commit de nada é o caso normal e saudável, e para a imensa maioria dos componentes é muito barato demais para medir.
Para de ser ok em duas situações: quando um componente faz trabalho caro durante sua renderização, ou quando uma subárvore é tão grande que até renderizações baratas se acumulam. Esses são exatamente os casos que Memoização aborda. Resista ao impulso de recorrer a ela antes de ter evidência, o que nos traz a medição.
Meça antes de otimizar
React já é rápido, e a reconciliação já mantém atualizações de DOM mínimas. O conselho do curso é direto: seja capaz de medir um problema de performance antes de aplicar uma técnica para resolvê-lo, e pese toda otimização contra a legibilidade do código que ela complica. Astúcia que ninguém consegue manter também é um custo.
Duas ferramentas cobrem a maioria das necessidades de medição:
- A aba Performance do navegador. Registre uma sessão, execute a interação lenta, pare, e inspecione o que executou e quanto tempo levou. Seu throttling de CPU (tente uma desaceleração 4x ou 6x) e throttling de rede deixam sua máquina rápida de desenvolvimento simular os dispositivos e conexões mais lentos que seus usuários realmente têm. Problemas invisíveis no seu laptop aparecem imediatamente.
- O React DevTools Profiler. Uma extensão de navegador do time React, vivendo em sua própria aba Profiler. O mesmo fluxo de registrar-agir-parar, mas a saída tem formato React: quais componentes renderizaram, por que renderizaram, quanto tempo cada um levou, e quanto tempo o commit levou. Para diagnosticar apps React especificamente, geralmente é a ferramenta mais direta.
StrictMode: um barreira para desenvolvimento
StrictMode é um componente que você envolve ao redor do seu app (ou qualquer subárvore) para ativar verificações apenas para desenvolvimento. É o wrapper que Setup coloca ao redor de <App /> em main.jsx.
Em desenvolvimento, StrictMode deliberadamente chama duas vezes suas funções de componente, junto com as outras funções que React espera serem puras: inicializadores de useState e useReducer, os updaters que você passa a um setter, reducers, e cálculos de useMemo. Executa cada effect uma vez extra, configurando, limpando, e configurando de novo, e reatribui callbacks de ref. Também avisa sobre APIs descontinuadas. Em um build de produção não faz nada, então nunca desacelera ou re-renderiza o app que seus usuários veem.
Executar duas vezes soa como sabotagem até você ver o que pega. Uma função de componente deveria ser pura: mesmas entradas, mesma saída, sem efeitos colaterais durante renderização. Se renderizar duas vezes produz um resultado diferente de renderizar uma vez, o componente estava escondendo um bug.
O curso demonstra isso com um componente que chama push em um array definido fora dele durante renderização; a página fica bem até qualquer mudança de estado re-executar a função e fazer push de um duplicado. Sob StrictMode o duplicado aparece na primeira carga, e o fix (copiar o array antes de modificá-lo, ou mover o trabalho para fora da renderização) segue disso.
Pureza cobre seus updaters
O mesmo requisito de pureza cobre o updater em setCount(c => c + 1), já que essa função também executa duas vezes.
O teste de re-execução do effect testa o mesmo contrato do outro lado. Effects é dono da regra de limpeza e mostra como fica sem uma sob StrictMode, quando um setInterval não limpo deixa você contando de dois em dois e vazando um timer a cada montagem.
Ver mais bugs em desenvolvimento parece estranho de início, mas é exatamente o objetivo. Todo bug que StrictMode expõe já existia e de outra forma teria esperado a produção para se apresentar.
Nota de versão
O comportamento do effect chegou no React 18: ao montar, StrictMode executa o setup de cada effect, depois sua limpeza, depois setup de novo. Artigos mais antigos descrevem StrictMode como apenas re-renderizando componentes. O curso escreve o wrapper como React.StrictMode; com named imports é <StrictMode>.
Envolva seu app em StrictMode enquanto você constrói: ele executa coisas duas vezes de propósito em desenvolvimento para ajudar você a encontrar bugs cedo, e se desativa no app finalizado.
Próximo: Memoização, onde igualdade referencial decide o que React consegue pular.

