Performance em React: memo, useMemo, useCallback e Profiler
React é declarativo e eficiente por padrão, mas aplicações grandes podem sofrer com renderizações desnecessárias. Nesta aula, você aprenderá as ferramentas essenciais para identificar e otimizar gargalos de performance em seus componentes. Abordaremos desde técnicas de memorização até ferramentas de diagnóstico.
React.memo: Evitando Renderizações Desnecessárias
React.memo é um componente de ordem superior (HOC) que memoriza um componente funcional. Se as props não mudarem, o componente não é renderizado novamente. Essa é a primeira linha de defesa contra renderizações desperdiçadas.
// Sem memo - renderiza sempre que o pai renderiza
function UserCard({ name, email }) {
console.log('UserCard renderizado');
return (
<div>
<h3>{name}</h3>
<p>{email}</p>
</div>
);
}
// Com memo - renderiza apenas se name ou email mudarem
const UserCard = React.memo(function UserCard({ name, email }) {
console.log('UserCard renderizado');
return (
<div>
<h3>{name}</h3>
<p>{email}</p>
</div>
);
});
// Componente pai
function App() {
const [count, setCount] = useState(0);
return (
<>
<button onClick={() => setCount(count + 1)}>
Cliques: {count}
</button>
{/* UserCard não renderiza ao clicar o botão */}
<UserCard name="João" email="joao@email.com" />
</>
);
}
Cuidado com objetos e funções: memo compara props superficialmente. Se você passa um objeto ou função nova a cada render, memo não funciona. Para esses casos, use a função de comparação customizada ou combine com useCallback.
// Problema: objeto novo a cada render
<UserCard user={{ name: "João", email: "j@email.com" }} /> // Sempre renderiza
// Solução: memoizar o objeto
const user = useMemo(() => ({ name: "João", email: "j@email.com" }), []);
<UserCard user={user} /> // Renderiza apenas uma vez
useMemo: Cachear Cálculos Pesados
useMemo é um hook que memoriza o resultado de uma computação. Use-o quando você tem operações custosas que não precisam rodar a cada render: filtros complexos, ordenações, ou cálculos matemáticos intensivos.
function ProductList({ products, filter }) {
// Sem useMemo: filtro executado a cada render
const filtered = products.filter(p => p.category === filter);
return (
<ul>
{filtered.map(p => <li key={p.id}>{p.name}</li>)}
</ul>
);
}
// Com useMemo: filtro executado apenas quando products ou filter mudam
function ProductList({ products, filter }) {
const filtered = useMemo(
() => products.filter(p => p.category === filter),
[products, filter] // Dependências
);
return (
<ul>
{filtered.map(p => <li key={p.id}>{p.name}</li>)}
</ul>
);
}
O array de dependências é crítico. Se omitir uma dependência, você terá dados desatualizados. Se incluir tudo, useMemo não traz benefício. A regra prática: inclua tudo que o callback usa e vem do escopo externo.
// Exemplo real com múltiplas dependências
const stats = useMemo(() => {
return {
total: products.reduce((sum, p) => sum + p.price, 0),
count: products.length,
avgPrice: products.reduce((sum, p) => sum + p.price, 0) / products.length
};
}, [products]); // products é a única dependência necessária
useCallback: Memorizar Funções
useCallback é para quando você precisa passar uma função como prop para um componente memo. Sem ele, a função é recriada a cada render, invalidando a memorização do componente filho.
// Problema: handleClick recriada a cada render
function Parent() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
};
// Child sempre renderiza porque handleClick é nova
return <Child onClick={handleClick} />;
}
// Solução: useCallback
function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(c => c + 1); // Use função anterior de estado
}, []); // Sem dependências, handleClick nunca muda
return <Child onClick={handleClick} />;
}
const Child = React.memo(function Child({ onClick }) {
console.log('Child renderizado');
return <button onClick={onClick}>Clique</button>;
});
Caso de uso real: callbacks em listas de itens. Sem useCallback, cada item renderiza quando o pai muda.
function TodoList({ todos, onRemove }) {
const handleRemove = useCallback((id) => {
onRemove(id);
}, [onRemove]); // Depende de onRemove do pai
return (
<ul>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onRemove={handleRemove}
/>
))}
</ul>
);
}
const TodoItem = React.memo(function TodoItem({ todo, onRemove }) {
return (
<li>
{todo.text}
<button onClick={() => onRemove(todo.id)}>X</button>
</li>
);
});
React DevTools Profiler: Diagnóstico Profissional
Não otimize sem dados. O Profiler do React DevTools mostra exatamente quais componentes renderizam, por quanto tempo e por quê. Isso diferencia um otimizador amador de um profissional.
Acesse o Profiler na aba "Profiler" do React DevTools. Clique em "Record", interaja com sua aplicação, depois pare a gravação. Você verá cada render: tempo de render, causas (prop ou state mudou), e comparação antes/depois.
// Componente para análise
function Dashboard() {
const [selected, setSelected] = useState(null);
const [data, setData] = useState([]);
useEffect(() => {
// Simula fetch
setTimeout(() => setData([...Array(100)].map((_, i) => ({ id: i }))), 1000);
}, []);
return (
<>
<button onClick={() => setSelected(selected ? null : 1)}>
Toggle (causa re-render de toda árvore)
</button>
<MemoizedChart data={data} />
<ItemList items={data} />
</>
);
}
const MemoizedChart = React.memo(function Chart({ data }) {
// Se não tiver memo, renderiza desnecessariamente
return <div>Gráfico com {data.length} itens</div>;
});
Dica profissional: Procure por "Render for no reason" no Profiler. Se um componente memo renderiza quando sua prop não mudou, está havendo renderização de pai desnecessária. Suba em pais e aplique técnicas de otimização em cascata.
A regra de ouro: otimize apenas o que o Profiler evidenciar como problema. Prematura optimization é raiz de todo mal.
Conclusão
As ferramentas apresentadas formam o arsenal do desenvolvedor React profissional. React.memo previne renderizações quando props não mudam; useMemo cacheia computações; useCallback estabiliza funções para filhos memoizados. Mas a verdadeira maestria vem do Profiler: meça sempre antes de otimizar. Componentes bem estruturados naturalmente têm boa performance. Otimizações pontuais, guiadas por dados, transformam aplicações lentas em rápidas.