<h2>Performance em React: memo, useMemo, useCallback e Profiler</h2>
<p>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.</p>
<h2>React.memo: Evitando Renderizações Desnecessárias</h2>
<p>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.</p>
<pre><code class="language-jsx">// 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" />
</>
);
}</code></pre>
<p><strong>Cuidado com objetos e funções</strong>: 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.</p>
<pre><code class="language-jsx">// 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</code></pre>
<h2>useMemo: Cachear Cálculos Pesados</h2>
<p>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.</p>
<pre><code class="language-jsx">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>
);
}</code></pre>
<p>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.</p>
<pre><code class="language-jsx">// 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</code></pre>
<h2>useCallback: Memorizar Funções</h2>
<p>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.</p>
<pre><code class="language-jsx">// 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>;
});</code></pre>
<p><strong>Caso de uso real</strong>: callbacks em listas de itens. Sem useCallback, cada item renderiza quando o pai muda.</p>
<pre><code class="language-jsx">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>
);
});</code></pre>
<h2>React DevTools Profiler: Diagnóstico Profissional</h2>
<p>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.</p>
<p>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.</p>
<pre><code class="language-jsx">// 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>;
});</code></pre>
<p><strong>Dica profissional</strong>: 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.</p>
<blockquote><p>A regra de ouro: otimize apenas o que o Profiler evidenciar como problema. Prematura optimization é raiz de todo mal.</p></blockquote>
<h2>Conclusão</h2>
<p>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.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://react.dev/reference/react/memo" target="_blank" rel="noopener noreferrer">React Documentation - memo</a></li>
<li><a href="https://react.dev/reference/react/useMemo" target="_blank" rel="noopener noreferrer">React Documentation - useMemo</a></li>
<li><a href="https://react.dev/reference/react/useCallback" target="_blank" rel="noopener noreferrer">React Documentation - useCallback</a></li>
<li><a href="https://react.dev/learn/react-developer-tools#profiler" target="_blank" rel="noopener noreferrer">React DevTools Profiler Guide</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Performance" target="_blank" rel="noopener noreferrer">Web Performance APIs - MDN</a></li>
</ul>