Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoAvançadocódigo testado

useMemo e useCallback: quando memorizar de verdade

O que cada um memoriza, por que envolver tudo deixa o app mais lento e a medição que mostra a partir de onde a memorização começa a compensar.

Rodolfo Mori14 min de leitura

Você abre o Profiler — o painel que mede o tempo de cada parte da tela — porque a busca está travando e espalha useMemo e useCallback pelo componente. O código cresce, mas a tela continua igual — ou fica mais lenta porque uma dependência muda em todo render. O problema não é faltar hook: é otimizar sem saber qual trabalho está sendo repetido.

Aqui você vai separar valor de função, medir o custo real e reconhecer quando a identidade estável ajuda. Os exemplos mantêm o painel de atrasos da transportadora Rota Certa. Tudo rodou num MacBook Pro M4 Pro, com Node 24.16.0, React 19.2.8 e jsdom 30.0.1, que simula no Node a árvore de elementos de uma página. montar() e act() controlam a raiz fora do navegador; gerarEncomendas(n) cria a base fictícia. Imports repetidos foram omitidos, e cada saída é a que apareceu no terminal.

Qual é a diferença entre useMemo e useCallback?

useMemo reutiliza o resultado de um cálculo; useCallback, a função. Os dois são hooks — funções especiais do React — usados como otimização. React é a biblioteca que executa componentes, funções que descrevem partes da tela; cada execução é um render.

Reutilizar um resultado recebe o nome de memoização. Manter o mesmo objeto ou função entre renders é estabilidade de referência: outra parte do código consegue reconhecer que recebeu exatamente a mesma peça, não apenas conteúdo parecido.

Imagine uma cozinha. As dependências são os ingredientes, useMemo guarda o prato pronto e useCallback guarda o mesmo cartão da receita. O cartão mantém a identidade da instrução, mas não cozinha antes de ser chamado. O limite: esse cache — a guarda temporária do resultado — não impede o componente de renderizar, pode ser descartado pelo React e nunca deve ser necessário para a lógica funcionar.

Antes da sintaxe, responda: o cálculo é caro e frequente? Alguém compara a referência dessa função? Se não, o hook só acrescenta manutenção. Aqui quem decide é a medição.

Como useMemo e useCallback comparam dependências?

O React compara cada dependência com Object.is. Se todas continuam iguais, devolve a referência guardada; se uma mudou, recalcula o useMemo ou substitui a função do useCallback. O painel recebe rota e tema, mas ranking e reagendamento dependem apenas da rota.

Experimente você mesmo

Antes de olhar a saída, preveja se array e função serão os mesmos quando apenas o tema mudar e quando a rota mudar. Depois confira os valores de true e false e relacione cada troca à lista [rota].

jsx
const encomendas = [
  { codigo: 'RC-000181', rota: 'sp-interior', atraso: 31 },
  { codigo: 'RC-000204', rota: 'sp-interior', atraso: 12 },
  { codigo: 'RC-000377', rota: 'rj-capital', atraso: 48 },
];

let anterior = {};

function PainelAtrasos({ rota, tema }) {
  const atrasadas = useMemo(
    () => encomendas.filter((e) => e.rota === rota).sort((a, b) => b.atraso - a.atraso),
    [rota],
  );

  const reagendar = useCallback((codigo) => `${codigo} reagendado na rota ${rota}`, [rota]);

  console.log(`render: rota=${rota} tema=${tema}`);
  console.log('  useMemo devolveu:', JSON.stringify(atrasadas.map((e) => e.codigo)));
  console.log('  useCallback devolveu:', reagendar('RC-000181'));
  console.log('  mesmo array do render anterior?  ', anterior.atrasadas === atrasadas);
  console.log('  mesma função do render anterior? ', anterior.reagendar === reagendar);
  anterior = { atrasadas, reagendar };

  return <p className={tema}>{atrasadas.length}</p>;
}

const { root } = montar();
await act(async () => root.render(<PainelAtrasos rota="sp-interior" tema="claro" />));
await act(async () => root.render(<PainelAtrasos rota="sp-interior" tema="escuro" />));
await act(async () => root.render(<PainelAtrasos rota="rj-capital" tema="escuro" />));
render: rota=sp-interior tema=claro useMemo devolveu: ["RC-000181","RC-000204"] useCallback devolveu: RC-000181 reagendado na rota sp-interior mesmo array do render anterior? false mesma função do render anterior? false render: rota=sp-interior tema=escuro useMemo devolveu: ["RC-000181","RC-000204"] useCallback devolveu: RC-000181 reagendado na rota sp-interior mesmo array do render anterior? true mesma função do render anterior? true render: rota=rj-capital tema=escuro useMemo devolveu: ["RC-000377"] useCallback devolveu: RC-000181 reagendado na rota rj-capital mesmo array do render anterior? false mesma função do render anterior? false

Leia as três respostas em ordem. No primeiro render não havia nada guardado. No segundo, o tema mudou e a rota não: o React devolveu o mesmo array e a mesma função do render anterior — repare que === não compara conteúdo, compara identidade. No terceiro, a rota mudou e os dois foram refeitos.

Quem decide isso é a lista de dependências. O React guarda o array [rota] do render anterior e compara posição a posição com Object.is — a mesma comparação do ===, com dois ajustes para NaN e -0. Se todas as posições baterem, ele devolve o valor velho e nem chama a sua função.

A documentação do React diz que useCallback(fn, deps) e useMemo(() => fn, deps) são equivalentes. Dá para conferir com os dois lado a lado, na mesma rota, com o tema alternando:

jsx
let anterior = {};

function Painel({ rota, tema }) {
  const viaCallback = useCallback((codigo) => `${codigo} na rota ${rota}`, [rota]);
  const viaMemo = useMemo(() => (codigo) => `${codigo} na rota ${rota}`, [rota]);

  console.log(
    `render ${tema}: useCallback estável? ${anterior.viaCallback === viaCallback}`,
    `| useMemo estável? ${anterior.viaMemo === viaMemo}`,
  );
  anterior = { viaCallback, viaMemo };
  return <p className={tema}>{viaCallback('RC-000181')}</p>;
}

const { root } = montar();
for (const tema of ['claro', 'escuro', 'claro']) {
  await act(async () => root.render(<Painel rota="sp-interior" tema={tema} />));
}
render claro: useCallback estável? false | useMemo estável? false render escuro: useCallback estável? true | useMemo estável? true render claro: useCallback estável? true | useMemo estável? true

Comportamento idêntico. Repare na diferença de sintaxe, que é onde as pessoas tropeçam: no useMemo você passa uma função que devolve o valor; no useCallback você passa a função que é o valor.

Equivalente por fora não quer dizer o mesmo código por dentro. No react-dom são duas funções separadas, mountMemo e mountCallback. Em desenvolvimento, StrictMode é o modo de diagnóstico que chama a fábrica do useMemo duas vezes para revelar cálculo impuro. O useCallback nunca chama a função; apenas a guarda.

jsx
const chamadas = { fabricaDoMemo: 0, funcaoDoCallback: 0 };

function Painel({ rota }) {
  const valor = useMemo(() => {
    chamadas.fabricaDoMemo++;
    return `top de ${rota}`;
  }, [rota]);

  const fn = useCallback(() => {
    chamadas.funcaoDoCallback++;
    return rota;
  }, [rota]);

  return <p data-fn={typeof fn}>{valor}</p>;
}

const { root } = montar();
await act(async () =>
  root.render(
    <StrictMode>
      <Painel rota="sp-interior" />
    </StrictMode>,
  ),
);

console.log('em StrictMode, no primeiro render:');
console.log(JSON.stringify(chamadas, null, 2));
em StrictMode, no primeiro render: { "fabricaDoMemo": 2, "funcaoDoCallback": 0 }

Duas chamadas contra nenhuma, num único render. Isso só machuca quem escreve alguma coisa que muda o mundo dentro do useMemo — e é exatamente esse o recado do React: a fábrica do useMemo precisa ser um cálculo puro, que só lê e devolve.

Até aqui: valor, função e dependências

useMemo chama uma fábrica para obter um valor; useCallback guarda a função sem chamá-la. Os dois reutilizam referências enquanto Object.is considera as dependências iguais.

O useMemo impede o componente de renderizar novamente?

Não. Ele pode evitar a fábrica e o recálculo, mas o componente ainda executa e o hook ainda compara dependências. O contador separa essas duas coisas em cinquenta renders com a mesma rota e tema alternado:

jsx
const encomendas = gerarEncomendas(5000);
let renders = 0;
let calculos = 0;

function PainelAtrasos({ rota, tema }) {
  renders++;

  const top = useMemo(() => {
    calculos++;
    return encomendas
      .filter((e) => e.rota === rota && !e.entregue)
      .sort((a, b) => b.atraso - a.atraso)
      .slice(0, 10);
  }, [rota]);

  return <p className={tema}>{top.length} encomendas no topo</p>;
}

const { root } = montar();
for (let i = 0; i < 50; i++) {
  const tema = i % 2 ? 'claro' : 'escuro';
  await act(async () => root.render(<PainelAtrasos rota="sp-interior" tema={tema} />));
}

console.log(`o componente rodou       : ${renders} vezes`);
console.log(`a função do useMemo rodou: ${calculos} vez`);
o componente rodou : 50 vezes a função do useMemo rodou: 1 vez

O cálculo rodou uma vez; o corpo do componente, cinquenta. E é justamente nos outros quarenta e nove que mora o custo que ninguém mede: em cada um deles o JavaScript criou o array literal [rota] e o React comparou esse array com o do render anterior. Dá para medir essa contabilidade sem React nenhum, reproduzindo o que o hook faz:

js
const encomendas = { tamanho: 100000 }; // qualquer objeto serve: o que compara é a identidade
const rota = 'sp-interior';

function contabilidade(anteriores) {
  const deps = [encomendas, rota]; // o array literal é novo em todo render
  if (anteriores === null) return deps;
  for (let i = 0; i < deps.length; i++) {
    if (!Object.is(deps[i], anteriores[i])) return deps;
  }
  return anteriores;
}

const VOLTAS = 20_000_000;
let anteriores = null;
for (let i = 0; i < 2_000_000; i++) anteriores = contabilidade(anteriores); // aquecimento

const amostras = [];
for (let r = 0; r < 5; r++) {
  const t0 = performance.now();
  for (let i = 0; i < VOLTAS; i++) anteriores = contabilidade(anteriores);
  amostras.push(((performance.now() - t0) / VOLTAS) * 1e6); // nanossegundos
}
amostras.sort((a, b) => a - b);
console.log(`mediana de 5 rodadas de ${VOLTAS.toLocaleString('pt-BR')} comparações:`);
console.log(`${amostras[2].toFixed(2)} ns por render, por useMemo`);
mediana de 5 rodadas de 20.000.000 comparações: 4.42 ns por render, por useMemo

Quatro nanossegundos e meio. Mil useMemo numa mesma tela custariam menos de cinco microssegundos por render — nada perto dos 16,7 ms que um quadro tem a 60 fps. Então não: o hook em si não é o que deixa o app lento.

Guarde essa conclusão, porque ela muda o argumento. Memorizar demais sai caro por dois outros motivos, e os dois aparecem mais para a frente neste artigo: uma lista de dependências a mais para manter correta em cada linha, e o recálculo que volta a acontecer em todo render quando essa lista está errada. O primeiro custa tempo do time; o segundo custa milissegundos de verdade.

Quando o useMemo começa a compensar?

Quando evita um cálculo caro e frequente o bastante para aparecer numa medição. Não existe quantidade universal: neste ambiente, o ganho fica claro na base de cem mil encomendas. Dois painéis permitem comparar o ranking com e sem cache:

Experimente você mesmo

Antes de ler o benchmark, ordene mentalmente as bases de 100, 1.000, 10.000 e 100.000 itens pela chance de produzir diferença perceptível. Depois confronte a previsão com os milissegundos e com a parcela do quadro de 16,7 ms.

jsx
function rankear(encomendas, rota) {
  return encomendas
    .filter((e) => e.rota === rota && !e.entregue)
    .sort((a, b) => b.atraso - a.atraso)
    .slice(0, 10);
}

function Lista({ top }) {
  return (
    <ul>
      {top.map((e) => (
        <li key={e.id}>{`${e.codigo} — ${e.atraso}h`}</li>
      ))}
    </ul>
  );
}

function PainelSemMemo({ encomendas, rota }) {
  return <Lista top={rankear(encomendas, rota)} />;
}

function PainelComMemo({ encomendas, rota }) {
  const top = useMemo(() => rankear(encomendas, rota), [encomendas, rota]);
  return <Lista top={top} />;
}

A medição descarta dez renders de aquecimento, mede quarenta, e repete tudo sete vezes para tirar a mediana — média sozinha é refém do coletor de lixo.

jsx
async function medir(Componente, encomendas, repeticoes) {
  const { root } = montar();
  for (let i = 0; i < 10; i++) {
    await act(async () =>
      root.render(<Componente encomendas={encomendas} rota="sp-interior" tick={i} />),
    );
  }
  const t0 = performance.now();
  for (let i = 0; i < repeticoes; i++) {
    await act(async () =>
      root.render(<Componente encomendas={encomendas} rota="sp-interior" tick={1000 + i} />),
    );
  }
  const total = performance.now() - t0;
  await act(async () => root.unmount());
  return total / repeticoes;
}

const mediana = (v) => [...v].sort((a, b) => a - b)[Math.floor(v.length / 2)];
const tamanhos = [100, 1000, 10000, 100000];
const dados = Object.fromEntries(tamanhos.map((n) => [n, { sem: [], com: [] }]));

for (let r = 0; r < 7; r++) {
  for (const n of tamanhos) {
    const encomendas = gerarEncomendas(n);
    dados[n].sem.push(await medir(PainelSemMemo, encomendas, 40));
    dados[n].com.push(await medir(PainelComMemo, encomendas, 40));
  }
}

console.log('encomendas | sem useMemo | com useMemo | diferença');
for (const n of tamanhos) {
  const sem = mediana(dados[n].sem);
  const com = mediana(dados[n].com);
  const d = sem - com;
  console.log(
    `${String(n).padStart(10)} | ${sem.toFixed(3).padStart(8)} ms | ${com.toFixed(3).padStart(8)} ms | ${(d >= 0 ? '+' : '') + d.toFixed(3)} ms`,
  );
}
encomendas | sem useMemo | com useMemo | diferença 100 | 0.106 ms | 0.100 ms | +0.006 ms 1000 | 0.116 ms | 0.101 ms | +0.015 ms 10000 | 0.314 ms | 0.100 ms | +0.214 ms 100000 | 2.563 ms | 0.073 ms | +2.490 ms

Traduzindo para o orçamento de um quadro a 60 fps, que é o número que importa quando a tela precisa parecer fluida:

encomendas sem useMemo com useMemo quanto do quadro de 16,7 ms o painel consome
100 0,106 ms 0,100 ms 0,6% → 0,6%
1.000 0,116 ms 0,101 ms 0,7% → 0,6%
10.000 0,314 ms 0,100 ms 1,9% → 0,6%
100.000 2,563 ms 0,073 ms 15,3% → 0,4%

Até dez mil encomendas a diferença é de centésimos de milissegundo: some no ruído da própria medição e nenhum usuário percebe. Em cem mil, o cálculo sozinho come um sexto do quadro, e aí o useMemo deixa de ser gosto pessoal.

Só que a tabela esconde a variável mais importante, que é a frequência. Dois milissegundos e meio num painel que renderiza uma vez por clique não significam nada. Os mesmos dois milissegundos e meio num painel que re-renderiza a cada tecla digitada num campo de busca viram travamento visível. Antes de memorizar, pergunte quantas vezes por segundo aquele componente roda. Se a resposta for “uma vez a cada ação do usuário”, não memorize nada — e vale o mesmo raciocínio para ordenar um array com sort, que é a parte cara desse cálculo.

Quando o useCallback realmente evita trabalho?

Quando alguém compara a referência da função: um filho envolvido por React.memo ou a lista de dependências de outro Hook. React.memo compara as props — os dados que o componente filho recebe — e pode pular o render quando elas permanecem iguais. O exemplo mede esse primeiro caso com três botões e conta cada render:

jsx
const conta = { semMemo: 0, memoComInline: 0, memoComCallback: 0 };

function BotaoSemMemo({ onReagendar }) {
  conta.semMemo++;
  return <button onClick={() => onReagendar('RC-000181')}>Reagendar</button>;
}

const BotaoMemoComInline = memo(function BotaoMemoComInline({ onReagendar }) {
  conta.memoComInline++;
  return <button onClick={() => onReagendar('RC-000181')}>Reagendar</button>;
});

const BotaoMemoComCallback = memo(function BotaoMemoComCallback({ onReagendar }) {
  conta.memoComCallback++;
  return <button onClick={() => onReagendar('RC-000181')}>Reagendar</button>;
});

function Painel({ rota, tema }) {
  const reagendarInline = (codigo) => `${codigo} reagendado na rota ${rota}`;
  const reagendarEstavel = useCallback(
    (codigo) => `${codigo} reagendado na rota ${rota}`,
    [rota],
  );

  return (
    <div className={tema}>
      <BotaoSemMemo onReagendar={reagendarInline} />
      <BotaoMemoComInline onReagendar={reagendarInline} />
      <BotaoMemoComCallback onReagendar={reagendarEstavel} />
    </div>
  );
}

const { root } = montar();
for (const tema of ['claro', 'escuro', 'claro', 'escuro']) {
  await act(async () => root.render(<Painel rota="sp-interior" tema={tema} />));
}

console.log('renders de cada botão em 4 renders do pai:');
console.log(JSON.stringify(conta, null, 2));
renders de cada botão em 4 renders do pai: { "semMemo": 4, "memoComInline": 4, "memoComCallback": 1 }

O botão do meio é o que ensina. Ele está dentro de React.memo e mesmo assim renderizou nas quatro vezes: a cada render do pai, reagendarInline é uma função nova, o React.memo compara a prop antiga com a nova, dá diferente, e ele desiste de pular. A memorização do componente foi anulada por uma prop instável.

Neste exemplo, useCallback só entrega ganho porque o filho usa React.memo. Uma função usada apenas no onClick do próprio componente não é comparada e não ganha nada. O outro caso central é a função entrar nas dependências de outro Hook — por exemplo, um useEffect que deve reagir só quando ela realmente muda. Hooks customizados também costumam estabilizar as funções que devolvem para permitir essas otimizações ao consumidor.

Por que uma dependência instável anula a memorização?

Porque uma nova referência falha na comparação por Object.is, mesmo com conteúdo igual. O useMemo então paga a comparação e o recálculo. O erro clássico é criar o objeto de filtros dentro do componente:

jsx
const encomendas = gerarEncomendas(100000);
const execucoes = { instavel: 0, estavel: 0 };

// desta vez o ranking aceita mais de uma rota, então recebe um objeto de filtros
function rankearPorFiltros(lista, filtros) {
  return lista
    .filter((e) => filtros.rotas.includes(e.rota) && !e.entregue)
    .sort((a, b) => b.atraso - a.atraso)
    .slice(0, 10);
}

function PainelInstavel({ tema }) {
  // objeto novo a cada render: a dependência nunca é a mesma
  const filtros = { rotas: ['sp-interior', 'rj-capital'] };

  const top = useMemo(() => {
    execucoes.instavel++;
    return rankearPorFiltros(encomendas, filtros);
  }, [filtros]);

  return <p className={tema}>{top.length}</p>;
}

A correção é mover o objeto para fora do componente. Ele não depende de nada que muda, então não tem por que nascer de novo a cada render:

jsx
// fora do componente: o mesmo objeto do primeiro ao último render
const FILTROS = { rotas: ['sp-interior', 'rj-capital'] };

function PainelEstavel({ tema }) {
  const top = useMemo(() => {
    execucoes.estavel++;
    return rankearPorFiltros(encomendas, FILTROS);
  }, []);

  return <p className={tema}>{top.length}</p>;
}

Cinquenta e um renders de cada versão, sobre cem mil encomendas:

dois objetos de mesmo conteúdo são o mesmo objeto? false instável: fábrica rodou 51x em 51 renders, 5.708 ms por render estável : fábrica rodou 1x em 51 renders, 0.070 ms por render

Cinquenta e uma execuções em cinquenta e um renders: o useMemo da versão instável nunca acertou o cache uma única vez. E o preço não é só o recálculo — são 5,7 ms contra 0,07 ms, oitenta vezes mais caro, com um array de dez itens sendo jogado fora a cada render para o coletor de lixo recolher depois.

É este o motivo pelo qual “memorizar tudo” deixa o app mais lento: quanto mais useMemo você escreve sem olhar, maior a chance de a lista de dependências ter um objeto, um array ou uma função criados ali mesmo — e cada um desses é um hook que só cobra e nunca entrega.

Quando o valor depende de algo que muda, existem três saídas, nesta ordem de preferência: subir a constante para fora do componente, como acima; quando os filtros vêm de props, depender dos campos primitivos em vez do objeto inteiro ([rota, incluirEntregues] no lugar de [filtros], porque string e booleano o Object.is compara por valor); ou, se o objeto precisa mesmo ser montado dentro do componente, memorizar o próprio objeto com um useMemo antes.

Até aqui: referência estável precisa ter consumidor

Memoização só entrega valor quando evita trabalho medido ou atende uma comparação real. Dependência instável invalida o cache; referência estável que ninguém compara também não ajuda.

Como corrigir nextCreate is not a function no useMemo?

Passe uma função que devolve o cálculo, não o resultado já calculado. Sem a arrow, rankear executa antes e entrega um array onde o React espera uma função:

jsx
const encomendas = gerarEncomendas(200);

function PainelAtrasos({ rota }) {
  // faltou a arrow: isto CHAMA rankear e entrega o ARRAY para o useMemo
  const top = useMemo(rankear(encomendas, rota), [rota]);
  return <p>{top.length}</p>;
}
/private/tmp/rota-certa/node_modules/react-dom/cjs/react-dom-client.development.js:8777 var nextValue = nextCreate(); ^

TypeError: nextCreate is not a function at mountMemo (/private/tmp/rota-certa/node_modules/react-dom/cjs/react-dom-client.development.js:8777:23) at Object.useMemo (/private/tmp/rota-certa/node_modules/react-dom/cjs/react-dom-client.development.js:26216:18) at process.env.NODE_ENV.exports.useMemo (/private/tmp/rota-certa/node_modules/react/cjs/react.development.js:1251:34) at PainelAtrasos (file:///private/tmp/rota-certa/07-erro.mjs:9:15) at Object.react_stack_bottom_frame (/private/tmp/rota-certa/node_modules/react-dom/cjs/react-dom-client.development.js:25904:20) at renderWithHooks (/private/tmp/rota-certa/node_modules/react-dom/cjs/react-dom-client.development.js:7662:22) at updateFunctionComponent (/private/tmp/rota-certa/node_modules/react-dom/cjs/react-dom-client.development.js:10166:19) at beginWork (/private/tmp/rota-certa/node_modules/react-dom/cjs/react-dom-client.development.js:11778:18) at runWithFiberInDEV (/private/tmp/rota-certa/node_modules/react-dom/cjs/react-dom-client.development.js:871:30) at performUnitOfWork (/private/tmp/rota-certa/node_modules/react-dom/cjs/react-dom-client.development.js:17641:22)

Node.js v24.16.0

nextCreate é o nome interno do primeiro argumento do useMemo — o mesmo nextCreate que aparece na primeira linha, no var nextValue = nextCreate();. A mensagem está dizendo, com todas as letras, que o React tentou chamar o que você passou e o que você passou era um array. Dos dez quadros da pilha, nove são internos do React; o seu é o quarto, at PainelAtrasos, e é o único que interessa. A correção é de dois caracteres:

diff
-  const top = useMemo(rankear(encomendas, rota), [rota]);
+  const top = useMemo(() => rankear(encomendas, rota), [rota]);

O que o React Compiler muda no useMemo e no useCallback?

Ele reduz a necessidade de memoização manual ao inserir caches durante a compilação. Isso continua sendo otimização, não garantia lógica: o componente precisa funcionar mesmo se um valor for recalculado. Na versão 1.0, o Compiler lê este componente sem hook:

jsx
function PainelAtrasos({ encomendas, rota }) {
  const top = encomendas
    .filter((e) => e.rota === rota && !e.entregue)
    .sort((a, b) => b.atraso - a.atraso)
    .slice(0, 10);

  return <Lista top={top} />;
}

E reescreve o arquivo. Rodando o babel-plugin-react-compiler 1.0.0 sobre esse componente, com @babel/core 8.0.1, sai isto:

import { c as _c } from "react/compiler-runtime"; import { jsx as _jsx } from "react/jsx-runtime"; function PainelAtrasos(t0) { const $ = _c(7); const { encomendas, rota } = t0; let t1; if ($[0] !== encomendas || $[1] !== rota) { let t2; if ($[3] !== rota) { t2 = e => e.rota === rota && !e.entregue; $[3] = rota; $[4] = t2; } else { t2 = $[4]; } t1 = encomendas.filter(t2).sort(_temp).slice(0, 10); $[0] = encomendas; $[1] = rota; $[2] = t1; } else { t1 = $[2]; } const top = t1; let t2; if ($[5] !== top) { t2 = /*#__PURE__*/_jsx(Lista, { top: top }); $[5] = top; $[6] = t2; } else { t2 = $[6]; } return t2; } function _temp(a, b) { return b.atraso - a.atraso; }

Vale ler devagar, porque é a mesma ideia deste artigo escrita por uma máquina. _c(7) pede sete posições de cache, guardadas junto do componente. A linha if ($[0] !== encomendas || $[1] !== rota) é exatamente a comparação que o useMemo faria. E o compilador memorizou três coisas onde você teria escrito uma: o ranking, a arrow passada para o filter e o próprio elemento <Lista>. A função de comparação do sort, que não depende de nada do render, ele simplesmente içou para fora do componente, como _temp.

Duas ressalvas honestas. O compilador desiste de otimizar componentes em que não consegue provar que o código segue as Regras do React — mutação de valor durante o render é o caso mais comum — e nesses arquivos você continua no regime manual. E ele não apaga o useMemo que você já escreveu: adotar o compilador num projeto antigo não limpa o código sozinho.

Quando vale a pena memorizar no React?

Quando uma medição mostra cálculo relevante ou quando outra API exige referência estável. A tabela resume onde a memorização paga seu custo:

situação memorizar? por quê
valor primitivo barato: uma soma, um toFixed, um template de string não o cálculo custa menos que a comparação das dependências
lista filtrada com poucos milhares de itens, num painel que renderiza por clique não pela medição acima, some no ruído
lista pesada recalculada a cada tecla, com dezenas de milhares de itens sim, useMemo é o único caso da tabela em que o número mudou de verdade
objeto ou array passado como prop para um filho em React.memo sim, useMemo sem identidade estável, o React.memo do filho não pula nada
função passada para um filho em React.memo sim, useCallback mesmo motivo — e só quando o React.memo existe
função ou objeto que entra nas dependências de um useEffect sim, ou suba para fora é a diferença entre o efeito rodar uma vez e rodar sempre
projeto com o React Compiler ligado não deixe o trabalho para ele e escreva o cálculo direto

O que estudar depois de useMemo e useCallback?

O próximo passo é extrair uma regra reutilizável sem expor essa complexidade. A decisão continua sendo numérica: meça antes, memorize depois e prefira correções estruturais, como subir constantes ou dividir o componente.

Na lição de hook customizado, você verá como esconder o cálculo e estabilizar com useCallback as funções devolvidas ao consumidor. O guia de React mostra o caminho inteiro, e o índice da trilha mantém a sequência lição a lição.

  • react
  • usememo
  • usecallback
  • performance
  • memo

Perguntas frequentes

useMemo evita que o componente renderize de novo?
Não. Ele evita o recálculo dentro do render, não o render. Quem pode pular a renderização de um filho é o React.memo; o useMemo e o useCallback só entregam a ele props com identidade estável para que essa comparação dê certo.
O React garante que o valor memorizado nunca some?
Não garante. A documentação lista os casos em que ele descarta: em desenvolvimento, quando você edita o arquivo do componente; em qualquer ambiente, quando o componente suspende durante a montagem inicial. Trate o useMemo como otimização: se o seu código quebra quando o valor é recalculado, o problema não era performance, era lógica no lugar errado.
Vale usar useCallback numa função que só é chamada no onClick do próprio componente?
Não. Se a função não é passada para um filho memorizado nem entra na lista de dependências de outro hook, ninguém compara a identidade dela. O useCallback ali só adiciona uma lista de dependências para manter.
Com o React Compiler ligado eu ainda escrevo useMemo?
Na maior parte dos casos, não. O compilador memoriza sozinho e não apaga o que você escreveu à mão. Vale manter o hook em pontos que o compilador não consegue provar seguros e desistiu de otimizar, e nos casos raros em que a identidade estável é exigida por uma biblioteca de fora.
Qual a diferença entre React.memo, useMemo e useCallback?
React.memo embrulha um componente e compara as props dele. useMemo guarda o resultado de um cálculo. useCallback guarda a função em si. Os dois hooks vivem dentro do componente; o React.memo vive em volta dele.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

Todo o código deste artigo foi executado em Node 24.16.0 com React 19.2.8 e jsdom 30.0.1, e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. React — useMemo — react.dev
  2. React — useCallback — react.dev
  3. React — React Compiler — react.dev

Continue por aqui