Por que um componente re-renderiza?
O React re-renderiza um componente quando:
- Seu estado muda (um
setState). - Suas props mudam.
- Seu componente pai re-renderiza (mesmo que suas props não mudem!).
Esse terceiro ponto é fundamental: por padrão, se um pai re-renderiza, todos os seus filhos re-renderizam em cascata. Quase sempre isso é barato e não acontece nada. Mas quando uma subárvore é pesada, convém ter ferramentas para evitar trabalho desnecessário.
React.memo: memoiza um componente
React.memo envolve um componente para que ele só re-renderize se suas props
mudarem (comparação superficial). Se o pai repinta mas as props do filho são as
mesmas, o React reutiliza o render anterior:
const ListaPesada = React.memo(function ListaPesada({ items }) {
// render custoso...
return <ul>{items.map((i) => <li key={i}>{i}</li>)}</ul>;
});
Só é útil se as props se mantiverem estáveis entre renders. Aí entram os dois hooks seguintes.
useMemo: memoiza um valor calculado
useMemo lembra o resultado de um cálculo e só o recalcula se mudar
alguma de suas dependências. Ideal para cálculos caros:
const ordenados = useMemo(() => {
return [...items].sort((a, b) => a - b); // cálculo caro
}, [items]); // só recalcula se 'items' mudar
Sem useMemo, esse sort rodaria a cada render. Com ele, só quando
items muda.
useCallback: memoiza uma função
A cada render são criadas funções novas. Se você passa uma função como prop
para um componente envolto em React.memo, essa prop "muda" sempre e a
memoização se quebra. useCallback estabiliza a identidade da função:
const manejarClique = useCallback(() => {
setSelecao(id);
}, [id]); // mesma função enquanto 'id' não mudar
useCallback(fn, deps) é equivalente a useMemo(() => fn, deps): um
memoiza a função, o outro seu resultado.
Regra mental:
useMemopara valores,useCallbackpara funções,React.memopara componentes. Os três comparam dependências/props para decidir se podem pular trabalho.
Exemplos
useMemo evita recalcular se as dependências não mudam
let calculos = 0;
function calcularCaro(n) { calculos++; return n * 2; }
// Render 1
const a = calcularCaro(21);
// Render 2 com a mesma entrada -> com useMemo NÃO recalcularia
console.log("resultado:", a, "| vezes calculado:", calculos);