Menu

Renderização no React: o que dispara uma re-renderização

Um componente React renderiza de novo quando o estado dele muda, quando o pai renderiza ou quando um context que ele lê muda. Entenda a diferença entre render e commit, o que o virtual DOM realmente significa e como evitar renderizações desnecessárias.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Um componente React renderiza de novo quando o próprio estado muda, quando o pai renderiza de novo ou quando um context que ele lê muda. Renderizar significa que o React chama de novo a função do seu componente para obter um JSX novo; depois ele atualiza só as partes da página que realmente mudaram.

Abra o Console abaixo da prévia e clique no botão. Os três componentes registram, inclusive Title, que não recebe prop nenhuma. Ele renderizou porque o pai renderizou.

O que dispara uma renderização

O React renderiza um componente exatamente por estes motivos:

  1. A primeira renderização. O app começa, ou o componente aparece na árvore pela primeira vez.
  2. O estado dele mudou. Você chamou um setter do useState ou um dispatch do useReducer com um valor novo.
  3. O pai dele renderizou. Por padrão, quando um componente renderiza, todo componente que ele retorna renderiza também, até o fim da árvore.
  4. Um context que ele lê mudou. Um componente que chama useContext(SomeContext) renderiza quando o provider mais próximo passa um novo value (veja useContext).

Uma crença comum é que um componente renderiza "porque as props mudaram". Isso inverte a causa. Props são argumentos que o pai passa enquanto renderiza, então props novas só podem chegar quando o pai renderiza. E o pai renderizar já basta: o Title acima não tem props e mesmo assim renderiza toda vez.

Definir o estado com o valor que ele já tem (comparado com Object.is) não inicia uma renderização dos filhos. O React ainda pode chamar aquele componente uma vez antes de perceber que nada mudou, mas descarta o resultado.

Render e commit

Toda atualização passa por duas fases:

  • Render. O React chama os seus componentes. Eles retornam JSX, que são só objetos descrevendo o que a tela deve mostrar. Nada muda na página ainda, e é por isso que a renderização precisa ser pura: nada de escrever no DOM, nada de requisições, nada de mudar variáveis fora do componente.
  • Commit. O React compara o novo resultado com o anterior e aplica as diferenças ao DOM: insere, remove ou atualiza só os nós que mudaram. Então o navegador pinta a tela e, depois disso, o React executa os seus efeitos.

O exemplo abaixo renderiza a cada clique, mas o React mantém o mesmo elemento <input>. Um efeito executa depois de cada commit e confere isso.

Digite algo no input e clique algumas vezes. O texto que você digitou continua, porque o React nunca substituiu o input: a única mudança no DOM a cada clique é o número dentro do <p>. O log também mostra a ordem, render primeiro e o efeito depois do commit.

O virtual DOM, sem rodeios

"Virtual DOM" é o nome popular dos objetos que os seus componentes retornam. <p>Items: {count}</p> compila para uma chamada que cria um objeto como { type: 'p', props: { children: ['Items: ', 1] } }. Depois de uma renderização, o React percorre a nova árvore desses objetos ao lado da anterior e, onde o tipo e a posição coincidem, mantém o nó existente do DOM e atualiza só os atributos e o texto que mudaram. Essa comparação se chama reconciliação.

O termo é impreciso por dois motivos. O React não guarda uma segunda cópia do DOM para comparar dois DOMs: ele compara os objetos de elemento com a própria árvore interna de componentes (a árvore de fibers). E o mesmo processo funciona para destinos que nem têm DOM, como o React Native. A documentação do React quase não usa a expressão e fala em renderizar e fazer o commit. O que importa na prática é a consequência: renderizar é barato comparado ao trabalho no DOM, porque a maioria das renderizações termina em uma atualização pequena do DOM ou em nenhuma.

Duas regras vêm de como a comparação funciona. Um tipo de elemento diferente no mesmo lugar (uma <div> trocada por uma <section>, ou ComponentA por ComponentB) destrói a subárvore antiga e o estado dela. E em listas, a key diz ao React qual item é qual, para que ele possa mover nós em vez de reconstruí-los.

Evitando renderizações desnecessárias

A maioria das renderizações extras não custa nada perceptível. Quando uma atrapalha de verdade, por exemplo uma lista grande que renderiza a cada tecla em outra parte da página, tente isto na ordem.

Mova o estado para baixo

Se só uma parte pequena da tela usa um pedaço de estado, mantenha esse estado em um componente que envolve só essa parte. Esta versão renderiza ProductList de novo a cada tecla:

export default function App() {
    const [text, setText] = useState('');
    return (
        <>
            <input value={text} onChange={(e) => setText(e.target.value)} />
            <ProductList />
        </>
    );
}

Mova o input e o estado dele para um componente próprio e a lista deixa de estar dentro do componente que renderiza:

Digite algumas letras: só SearchBox registra.

Passe children

Às vezes o estado precisa morar em um wrapper em volta da parte cara, como um painel que liga e desliga um destaque. Faça o wrapper aceitar children. O pai cria os elementos filhos e, como o pai não renderiza quando o estado do wrapper muda, esses elementos são os mesmos objetos de antes, então o React os pula.

Clique no toggle e só Highlighter registra. Agora mova <Article /> para dentro do JSX de Highlighter no lugar de {children}, e cada clique também registra render Article.

Memoize

Quando nenhuma das reorganizações serve, envolva o filho em memo. O React então compara as props dele com as anteriores e pula a renderização quando todas são iguais. Objetos e funções criados durante a renderização são novos toda vez, então o memo normalmente vem acompanhado de useMemo ou useCallback para essas props. A página do React.memo mostra isso com exemplos executáveis.

import { memo } from 'react';

const ProductList = memo(function ProductList({ category }) {
    // skipped while category stays the same
});

O React Compiler pode adicionar esse tipo de memoização para você no build, mas reorganizar o estado continua sendo a primeira coisa a tentar: isso elimina o trabalho em vez de guardá-lo em cache.

Renderizações normalmente não são problema

Uma renderização é uma chamada de função que retorna objetos. O React executa milhares delas rapidamente, e uma renderização que produz o mesmo resultado não muda o DOM. Não adicione memo em todo lugar "por segurança": cada comparação tem o próprio custo e deixa o código mais difícil de ler. Meça primeiro. O Profiler do React DevTools mostra quais componentes renderizaram, por quê e quanto tempo cada um levou.

Perguntas frequentes

O que faz um componente React renderizar de novo?

Três coisas: o próprio estado muda, o pai renderiza ou um context que ele lê com useContext recebe um valor novo. Props não são um gatilho separado: props novas só chegam porque o pai renderizou.

Um filho renderiza de novo quando o pai renderiza?

Sim, por padrão todo componente dentro de um pai que renderiza também renderiza, mesmo que as props dele não tenham mudado. Envolver o filho em memo permite que o React o pule quando as props são as mesmas da última vez.

O que é o virtual DOM no React?

É um nome informal para a árvore de objetos JavaScript comuns (elementos React) que os seus componentes retornam. O React compara a nova árvore com a anterior e muda só os nós reais do DOM que são diferentes.

Re-renderizar é ruim para a performance?

Normalmente não. Uma renderização é uma chamada de função que produz objetos, e o React só mexe no DOM onde o resultado mudou. Otimize quando uma renderização for comprovadamente lenta, por exemplo medindo com o Profiler do React DevTools.

Qual é a diferença entre render e commit?

Renderizar é o React chamar os seus componentes para descobrir como a tela deve ficar. Fazer o commit é o React aplicar as diferenças ao DOM. Uma renderização que produz o mesmo resultado não faz nenhuma mudança no DOM.

Ilustração das linguagens de programação do Coddy

Aprenda a programar com o Coddy

COMEÇAR