Memoização no React: Guia breve (useMemo, useCallback, React.memo e além)

Memoização é uma técnica de otimização que guarda resultados de cálculos ou renders anteriores e os reutiliza quando os mesmos inputs aparecem novamente. No universo React, ela aparece em várias formas (useMemo, useCallback, React.memo) e pode ser poderosa quando aplicada com critério. Este guia explora o porquê, o como, exemplos práticos, armadilhas e alternativas.

Logotipo React em um editor de códigos
Foto de Lautaro Andreani na Unsplash

O que é memoização?

Esta é uma técnica utilizada para dar velocidade à sistemas computacionais, armazenando o resultado de funções que podem ser consideradas caras para a aplicação. Assim o sistema obtém o valor já computado quando há a necessidade de reutilizar aquele item. Este termo foi cunhado por Donald Michie em 1968 e quer dizer algo como “à ser lembrado” (tradução livre do inglês).

Por que memoizar em React?

O React reconstrói (re-renderiza) componentes quando o estado ou props mudam (isso é desejável e necessário). Mas nem todo re-render custa o mesmo: alguns componentes executam cálculos pesados, criam grandes árvores virtuais, inicializam bibliotecas de terceiros ou provocam efeitos colaterais de alto custo. A memoização ajuda quando:

  • Você tem cálculos caros em cada render (filtros, transformações, agregações).
  • Componentes filhos re-renderizam desnecessariamente porque recebem novas referências (objetos/funções) vindas do pai.
  • Você precisa estabilizar callbacks passados a componentes otimizados (que fazem comparação referencial).

Em síntese: memoize para reduzir trabalho (cálculos) ou re-renderizações custosas.


Principais APIs e técnicas em React

1. useMemo: memoizar valores/cálculos

useMemo armazena o resultado de uma função entre renders enquanto a lista de dependências não mudar.

JavaScript
import { useMemo } from "react";

function ExpensiveList({ items, filter }) {
  const filtered = useMemo(
    () => items.filter(x => expensiveCheck(x, filter)),
    [items, filter]
  );

  return <List items={filtered} />;
}

Quando usar: cálculos pesados que dependem de props/estado.
O que NÃO é: useMemo não impede o componente de re-renderizar — ele apenas evita recalcular o valor. Para prevenir re-render do filho, combine com React.memo ou useCallback.


2. useCallback: memoizar funções

useCallback(fn, deps) retorna a mesma função entre renders enquanto deps não mudarem. É, na prática, equivalente a useMemo(() => fn, deps).

JavaScript
import { useCallback } from "react";

function Parent() {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    setCount(c => c + 1);
  }, []);

  return <Child onClick={handleClick} />;
}

Quando usar: ao passar funções para componentes que usam comparação referencial (por exemplo, React.memo) ou quando você quer estabilizar dependências de outros hooks.


3. React.memo: memoizar componente funcional

React.memo(Component) cria uma versão memoizada que só re-renderiza se suas props mudarem (comparação superficial com Object.is). Útil para componentes puros que recebem props primitivas ou referências estáveis.

JavaScript
const Expensive = React.memo(function Expensive({ items }) {
  // render pesado
});

Você pode fornecer uma função de comparação customizada (areEqual(prevProps, nextProps)) para casos avançados.


Exemplos práticos – padrões comuns

1. Evitar re-render de listas com React.memo + useCallback

JavaScript
const Row = React.memo(function Row({ item, onSelect }) {
  // render do row
});

function Table({ items }) {
  const handleSelect = useCallback(id => {
    // ...
  }, []);

  return (
    <div>
      {items.map(it => (
        <Row key={it.id} item={it} onSelect={handleSelect} />
      ))}
    </div>
  );
}

Sem useCallback, handleSelect seria recriado a cada render do Table, invalidando o React.memo(Row).


2. Memoizando cálculos pesados

JavaScript
function Stats({ data }) {
  const stats = useMemo(() => computeHeavyStats(data), [data]);
  return <Summary stats={stats} />;
}

Medir se computeHeavyStats é realmente custoso: console.time ou o Profiler do React. Se data muda raramente, useMemo compensa.


3. Memoizar render condicional com keys estáveis

Ao renderizar componentes caros com condicionais, prefira manter a mesma instância e controlar visibilidade via CSS ou flags, em vez de desmontar/remontar frequentemente — isso reduz custo de mount/unmount.


Como medir o impacto

Antes de memoizar em massa, meça. Use:

  • React DevTools Profiler (marque commits e compare timelines).
  • Chrome DevTools Performance / Lighthouse.
  • console.time para funções específicas.

Procure “wasted renders” no Profiler: se o componente está re-renderizando sem necessidade e isso corresponde a trabalho pesado, memoização é candidata. Caso contrário, é provável que não traga ganho perceptível.


Desvantagens e riscos da memoização

  1. Overhead de memória: caches aumentam uso de memória; memoizar tudo pode criar leaks.
  2. Custo de comparação: React.memo faz comparação superficial. Comparações customizadas custam CPU; comparar objetos profundos pode ser mais caro do que re-renderizar.
  3. Complexidade mental: o código fica mais difícil de entender; outros devs podem quebrar otimizações passando referências novas por engano.
  4. Stale closures / referências: usar useCallback com dependências erradas provoca closures que “capturam” valores antigos.
  5. Montagem/desmontagem: evitar re-render via memo pode esconder problemas de arquitetura que seriam melhor resolvidos por composição de componentes.
  6. Falso senso de segurança: otimizações prematuras causam manutenção difícil sem ganho real.

Acredito que este é um ponto importante a ser reforçado, pois este foi meu principal aprendizado enquanto estava me aprofundando para escrever: memoize quando medido e justificado, não por hábito. Nos últimos anos estive aplicando essas técnicas sem ao menos medir antes e avaliar com mais profundidade cada caso e estudá-lo novamente, me fez repensar em meus hábitos como um desenvolvedor frontend.


Boas práticas e checklist rápido

  • Meça antes (Profiler, console.time).
  • Memoize apenas cálculos caros e componentes com render pesado.
  • Prefira useMemo para valores/cálculos e useCallback para funções passadas a filhos memoizados.
  • Evite memoizar objetos literais inline, em vez disso, use useMemo para criar a referência.
  • Tenha cuidado com dependências: utilize ESLint (plugin react-hooks) para ajudar a manter array de dependências correto.

Compatibilidade e histórico

  • React.memo foi introduzido em React 16.6 (outubro de 2018).
  • Hooks (useMemo, useCallback) foram adicionados em React 16.8 (fevereiro de 2019). As documentações atuais explicam usos e armadilhas.

Se você mantém bibliotecas que suportam versões antigas (<16.8), não terá hooks; nesse caso, padrões antigos (PureComponent, shouldComponentUpdate) ainda se aplicam.


Exemplos anti-patterns (e como consertá-los)

Anti-pattern: memoizar tudo “por segurança”.

JavaScript
const A = React.memo(function A({ x }) { /* ... */ });
const B = React.memo(function B({ y }) { /* ... */ });

Sem medir, você pode introduzir overhead desnecessário. Em muitos casos, a solução é re-estruturar o componente (lift state, split componentes) em vez de memoizar tudo.

Anti-pattern: depender de useMemo para evitar re-renders em vez de estabilizar referências.

JavaScript
// ruim
<Child data={items.map(x => transform(x))} />
// melhor
const transformed = useMemo(() => items.map(x => transform(x)), [items]);
<Child data={transformed} />

Ferramentas e bibliotecas úteis

  • React DevTools Profiler: para medir commits e render cost.
  • ESLint plugin eslint-plugin-react-hooks: ajuda a manter dependências corretas.

Conclusão

Memoização no React é uma técnica poderosa, porém de uso cirúrgico. Ela resolve problemas reais, focada para cálculos caros, renders custosos e mudanças de referência que disparam re-renderizações, mas traz trade-offs (memória, complexidade e risco de bugs sutis). O fluxo correto é: medir → compreender o custo → aplicar memoização pontual → documentar.


📢 Curtiu essa explicação?
Me siga no LinkedIn para mais conteúdos práticos sobre desenvolvimento frontend.


Referências