Performance no React: memo, useMemo, useCallback
1. Por que a performance importa no React?
O React é conhecido por sua eficiência em atualizar a interface do usuário, mas essa eficiência não é automática. Toda vez que o estado de um componente muda, o React re-renderiza o componente e todos os seus filhos. Esse processo envolve reconciliação (reconciliation) e diffing — algoritmos que comparam a árvore virtual do DOM anterior com a nova para aplicar apenas as diferenças necessárias.
O problema surge quando componentes que não precisam ser atualizados acabam sendo re-renderizados. Em uma aplicação pequena, isso pode passar despercebido. Mas em sistemas complexos — com listas extensas, gráficos ou formulários com muitos campos — renderizações desnecessárias podem causar lentidão perceptível, travamentos na interface e consumo excessivo de memória.
É aqui que entram três ferramentas essenciais do ecossistema React: React.memo, useMemo e useCallback.
2. React.memo: evitando re-renderizações de componentes
React.memo é um higher-order component (HOC) que envolve um componente funcional e impede sua re-renderização se as props não mudarem. Ele faz uma comparação superficial (shallow comparison) das props — verifica se as referências ou valores primitivos são os mesmos da renderização anterior.
import React from 'react';
const ListItem = React.memo(({ item, onRemove }) => {
console.log(`Renderizando item: ${item.id}`);
return (
<li>
{item.name}
<button onClick={() => onRemove(item.id)}>Remover</button>
</li>
);
});
export default ListItem;
Neste exemplo, ListItem só será re-renderizado se item ou onRemove mudarem. Se o componente pai re-renderizar sem alterar essas props, o ListItem permanece inalterado.
Quando funciona e quando falha: A comparação superficial funciona bem para props primitivas (string, number, boolean). Para objetos, arrays ou funções, a comparação é por referência — se o pai criar um novo objeto em toda renderização, o React.memo não impedirá a re-renderização. É aí que useCallback e useMemo entram em cena.
3. useMemo: memorizando valores computados
Enquanto React.memo memoriza componentes, useMemo memoriza valores. Ele recebe uma função de cálculo e um array de dependências. O resultado só é recalculado quando as dependências mudam.
import React, { useMemo } from 'react';
const UserList = ({ users, searchTerm }) => {
const filteredUsers = useMemo(() => {
console.log('Filtrando usuários...');
return users.filter(user =>
user.name.toLowerCase().includes(searchTerm.toLowerCase())
);
}, [users, searchTerm]);
return (
<ul>
{filteredUsers.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
};
Sem useMemo, o filtro seria executado em toda renderização, mesmo que users e searchTerm não tivessem mudado. Com useMemo, o cálculo só ocorre quando pelo menos uma das dependências muda.
Cuidados importantes: Memorização tem custo — o React precisa armazenar e comparar o valor anterior. Use useMemo apenas para cálculos realmente pesados (processamento de arrays grandes, transformações complexas de dados, operações matemáticas intensivas). Para cálculos simples, o custo da memorização pode superar o benefício.
4. useCallback: memorizando funções para evitar novas referências
Funções inline são um dos principais causadores de re-renderizações indesejadas. Toda vez que um componente pai re-renderiza, funções definidas dentro dele são recriadas — mesmo que o código seja idêntico ao anterior. Isso quebra a memorização de React.memo nos componentes filhos.
import React, { useState, useCallback } from 'react';
import ListItem from './ListItem';
const ListContainer = () => {
const [items, setItems] = useState([
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' }
]);
const [count, setCount] = useState(0);
// Sem useCallback: nova referência em toda renderização
const handleRemove = useCallback((id) => {
setItems(prev => prev.filter(item => item.id !== id));
}, []);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>
Contador: {count}
</button>
<ul>
{items.map(item => (
<ListItem key={item.id} item={item} onRemove={handleRemove} />
))}
</ul>
</div>
);
};
Aqui, handleRemove mantém a mesma referência entre renderizações graças ao useCallback. Combinado com React.memo no ListItem, o clique no contador não força a re-renderização dos itens da lista.
Sinergia com React.memo: useCallback é mais útil quando usado em conjunto com React.memo. Se o componente filho não estiver memorizado, a função estável não trará benefício de performance — mas ainda pode ser útil para efeitos colaterais ou hooks como useEffect.
5. Mitos, armadilhas e boas práticas comuns
Mito: "Usar memo/useMemo/useCallback sempre melhora a performance" — Falso. Cada uma dessas ferramentas tem custo computacional. React.memo precisa comparar props, useMemo armazena valores em cache, useCallback mantém referências. Em componentes simples ou cálculos baratos, o custo pode superar o benefício.
Armadilha: Dependências vazias ou incorretas — Dependências incorretas causam bugs silenciosos. Use o ESLint com o plugin eslint-plugin-react-hooks para garantir dependências corretas.
Quando NÃO usar:
- Componentes que sempre recebem props diferentes (ex: listas com keys únicas)
- Cálculos leves (operações matemáticas simples, formatação de strings)
- Funções passadas para elementos nativos do DOM (como onClick em <button>)
- Componentes que re-renderizam com pouca frequência
6. Ferramentas de diagnóstico e profiling
Antes de otimizar, meça. O React DevTools Profiler é a ferramenta mais direta para identificar re-renderizações desnecessárias:
// Exemplo de uso com performance.now()
const startTime = performance.now();
// código a ser medido
const endTime = performance.now();
console.log(`Tempo de execução: ${endTime - startTime}ms`);
Bibliotecas como why-did-you-render podem ser injetadas durante o desenvolvimento para alertar sobre re-renderizações suspeitas:
import whyDidYouRender from '@welldone-software/why-did-you-render';
whyDidYouRender(React, {
trackAllPureComponents: true,
});
7. Estratégias avançadas de otimização
Combinação de useMemo com useCallback em contextos complexos: Quando um contexto fornece objetos ou arrays, use useMemo para estabilizá-los e useCallback para funções:
const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
const contextValue = useMemo(() => ({
state,
dispatch
}), [state, dispatch]);
return (
<AppContext.Provider value={contextValue}>
{children}
</AppContext.Provider>
);
};
useMemo para estabilizar objetos passados como props: Se um componente filho recebe um objeto configurado dentro do pai, encapsule-o em useMemo:
const config = useMemo(() => ({
theme: 'dark',
language: 'pt-BR'
}), []);
Complemento com lazy loading e code splitting: Memorização não substitui estratégias como React.lazy e Suspense para carregamento sob demanda de componentes pesados.
Lembre-se: otimização prematura é a raiz de todo mal. Meça primeiro, identifique gargalos reais e só então aplique React.memo, useMemo e useCallback nos pontos críticos. A performance de uma aplicação React não vem de aplicar essas ferramentas em todo lugar, mas de usá-las com inteligência cirúrgica onde realmente fazem diferença.
Referências
- React.memo – Documentação Oficial do React — Explicação completa do HOC React.memo, incluindo comparação personalizada e boas práticas.
- useMemo – Documentação Oficial do React — Guia oficial sobre o hook useMemo com exemplos de uso e armadilhas comuns.
- useCallback – Documentação Oficial do React — Documentação detalhada do hook useCallback, incluindo sinergia com React.memo.
- Optimizing Performance – React Docs (Beta) — Seção sobre renderização e commit no React, explicando o processo de reconciliação.
- Why Did You Render – Biblioteca de Debug — Ferramenta para identificar re-renderizações desnecessárias durante o desenvolvimento.
- React DevTools Profiler – Guia de Uso — Como usar o Profiler do React DevTools para medir performance e identificar gargalos.