Skip to content
This page has been auto-translated and may contain errors.View in English

Memoização

O capítulo rendering é o modelo mental no qual este se baseia: quando o estado muda, React re-executa o componente e tudo abaixo dele, e normalmente isso é ótimo porque a maioria das renderizações é rápida. Às vezes não é. Um cálculo no topo de um componente pode levar tempo suficiente para você notar, ou uma subárvore de componentes pode ser cara o bastante para que re-renderizá-la a cada digitação deixe o app lento.

Para esses casos medidos, React oferece três ferramentas: useMemo, memo e useCallback, e todas as três fazem variações da mesma coisa: deixam React se lembrar de um resultado da renderização anterior e pular o retrabalho. Para usar bem qualquer uma delas, você primeiro precisa saber como JavaScript decide se duas coisas são "as mesmas".

Tipos de valor, tipos de referência e igualdade referencial

JavaScript divide valores em duas famílias. Tipos de valor (também chamados primitivos) são booleanos, números, strings e os que você encontra menos frequentemente: undefined, null, symbol e bigint. Dois deles são iguais quando têm o mesmo valor do mesmo tipo: 1 === 1 é verdadeiro. Tipos de referência (também chamados tipos complexos) são objetos, arrays e funções. Dois deles são iguais apenas quando são literalmente a mesma coisa na memória. Parecer idêntico não conta para nada.

jsx
const a = { name: 'João' }
const b = { name: 'João' }

console.log(Object.is(a, b)) // false: dois objetos separados na memória

const c = a
console.log(Object.is(a, c)) // true: ambos apontam para o mesmo objeto

Object.is é o que React usa por baixo dos panos para essas comparações, e se comporta quase identicamente a === aqui. Até mesmo {} !== {}: dois literais de objeto vazio são duas alocações distintas, então falham na verificação. Isso é chamado igualdade referencial, e é o conceito raiz de todo este capítulo.

Agora conecte isso a renderização. Um componente é uma função, e cada renderização re-executa seu corpo do topo. Qualquer objeto, array ou função criada naquele corpo é totalmente novo em cada renderização:

jsx
function App() {
  const styles = { backgroundColor: 'black' } // novo objeto a cada renderização
  function increment() { /* ... */ }          // nova função a cada renderização
  // ...
}

Os conteúdos podem ser idênticos de uma renderização para a próxima, mas as referências nunca são. Qualquer comparação que pergunta "essa prop mudou?" verificando Object.is responderá sim toda vez. Tudo o que se segue é sobre controlar quando essas referências mudam.

useMemo: pule um cálculo custoso

O primeiro uso de memoização não tem nada a ver com referências ainda. Um cálculo no nível superior de um componente executa em cada renderização, até mesmo renderizações acionadas por estado que o cálculo nunca toca. Se ordenar uma lista grande de produtos leva uma fatia notável de tempo, um botão contador em outro lugar do mesmo componente fica travado, porque cada clique re-executa a ordenação sem motivo algum.

useMemo resolve isso com a mesma forma de um efeito: uma função mais um array de dependências. React chama a função, se lembra do que ela retorna, e em renderizações posteriores devolve o valor memorizado sem chamar a função novamente, contanto que nada no array de dependências tenha mudado.

jsx
import { useMemo, useState } from 'react'

function ProductList({ productsData }) {
  const [count, setCount] = useState(0)

  const sortedProducts = useMemo(() => {
    return [...productsData].sort((a, b) => a.name.localeCompare(b.name))
  }, [productsData])

  // ...
}

O array de dependências contém tudo que o cálculo lê: aqui, apenas productsData. Mudar count re-renderiza o componente, mas React vê que productsData é a mesma e pula a ordenação inteiramente. Apenas um novo array productsData dispara um recálculo. Na demo do curso, isso reduziu a ordenação de um lag visível em cada clique para zero milissegundos em renderizações que não tocaram os dados.

memo: pule a re-renderização de um filho

useMemo coloca em cache um valor dentro de um componente. memo opera um nível acima: pode pular a re-renderização de um componente inteiro. É um componente de ordem superior, uma função que pega seu componente e retorna uma versão aprimorada dele. Você costuma vê-lo escrito React.memo em bases de código mais antigas; a importação moderna é nomeada.

jsx
import { memo } from 'react'

function Product({ name, styles }) {
  // um corpo de componente custoso
}

export default memo(Product)

O componente aprimorado compara suas props anteriores com suas props recebidas. Se cada prop é a mesma, React pula a re-renderização para aquela renderização pai, e a subárvore que teria renderizado é pulada com ela (com exceções, cobertas abaixo). Na app demo do curso, envolver um componente em memo reduziu uma mudança de estado de trinta e uma renderizações para uma, porque a subárvore inteira não tocada foi pulada em uma única decisão.

"A mesma" significa igualdade rasa: cada prop é comparada com Object.is. Uma prop string ou number compara por valor e se comporta como você esperaria. E é exatamente aí que o problema começa.

Por que memo silenciosamente para de funcionar

Passe a um componente envolvido com memo uma prop de objeto, e a otimização morre silenciosamente:

jsx
function App() {
  const [darkMode, setDarkMode] = useState(false)
  const [count, setCount] = useState(0)

  const styles = {
    backgroundColor: darkMode ? '#222' : '#fff',
    color: darkMode ? '#fff' : '#000',
  }

  return <Product styles={styles} /* ... */ />
}

Toda vez que count muda, App re-renderiza e constrói um objeto styles novo. Os valores dentro são idênticos, mas a igualdade referencial não se importa: Object.is(oldStyles, newStyles) é falso, memo conclui que as props mudaram, e Product re-renderiza mesmo assim. Nada causa erro, nada avisa. A memoização não faz nada.

Este é o segundo trabalho de useMemo: manter uma referência entre renderizações para que uma comparação possa ter sucesso. Envolver a criação do objeto e dar a ela a única dependência com a qual ela de fato varia:

jsx
const styles = useMemo(() => ({
  backgroundColor: darkMode ? '#222' : '#fff',
  color: darkMode ? '#fff' : '#000',
}), [darkMode])

Agora styles é o mesmo objeto, por referência, em cada renderização até que darkMode mude. A verificação memo passa, e Product fica pulado quando apenas o contador se move. As duas ferramentas se combinam: memo no filho pergunta "as props mudaram?", e useMemo no pai garante que a resposta possa ser não.

children é uma prop como qualquer outra, e isso pega as pessoas. Filhos JSX escritos no pai são um novo objeto de elemento em cada renderização do pai, então <Card><Chart /></Card> passa a Card uma nova referência children toda vez e memo em Card nunca tem sucesso, por mais estáveis que as outras props sejam. O padrão estrutural que rendering descreve funciona do outro lado: quando o elemento é criado acima do componente que possui o estado, a re-renderização daquele componente vê o mesmo objeto de elemento e pula a ramificação sem qualquer memoização.

O que memo não pula

memo governa apenas as props que chegam do pai. Um componente memoizado ainda re-renderiza quando seu próprio estado muda e quando um contexto que lê atualiza. O contexto também alcança uma subárvore pulada: um descendente consumindo um contexto mudado re-renderiza mesmo que o invólucro memoizado acima dele tenha sido pulado.

useCallback: useMemo para funções

Funções também são tipos de referência, então uma prop de callback quebra memo exatamente da mesma forma. Defina function increment() {...} em um corpo de componente, passe-a para baixo, e o filho recebe uma referência de função totalmente nova em cada renderização do pai. useCallback existe precisamente para isso: memoiza a função em si.

jsx
const [selectedProduct, setSelectedProduct] = useState(null)

const chooseProduct = useCallback((id) => {
  console.log('Novo produto selecionado')
  setSelectedProduct(id)
}, [])

Passe chooseProduct para baixo a um filho envolvido com memo e a otimização se mantém: useCallback(fn, deps) mantém a mesma referência de função entre renderizações até que uma dependência mude. A relação com useMemo é próxima: com useMemo o valor memorizado é qualquer coisa que sua função retorna, enquanto com useCallback o valor memorizado é a função que você passa.

Um detalhe a levar consigo: funções de definidor de estado como setSelectedProduct já têm uma identidade estável, React a garante. Passar um definidor cru para baixo como uma prop é seguro sem useCallback; o hook ganha seu lugar quando você envolve o definidor em sua própria função que faz trabalho extra primeiro.

Uma referência de função estável também importa além de memo. Se uma função aparece em um array de dependência useEffect, uma referência nova a cada renderização significa que o efeito re-executa cada renderização, como effects cobre. useCallback também silencia isso.

Meça primeiro

Confirme que há um problema de performance antes de recorrer a qualquer uma dessas ferramentas. Rendering faz o caso e expõe o fluxo de trabalho: a maioria das renderizações é barata, uma renderização que não muda nada nunca alcança o DOM, e o Profiler com CPU acelerada é como você encontra as que de fato custam algo. Memoizar um cálculo trivial como data.length custa mais do que economiza, porque o caching e a comparação são eles próprios trabalho. Corrija apenas o que pode medir, e corrija renderizações lentas antes de gastar energia reduzindo o número de renderizações.

Nota de versão

O React Compiler, que chegou a 1.0 em outubro de 2025, automatiza a maioria do que este capítulo cobre: analisa componentes em tempo de compilação e insere memoização automaticamente, então useMemo, useCallback e memo feitos à mão se tornam largamente desnecessários em projetos que o adotam. Os conceitos continuam valendo a pena entender, tanto porque bases de código existentes estão cheias desses hooks quanto porque saber o que o compilador faz por você torna seu comportamento legível em vez de mágico.

Mecanicamente, useMemo armazena o valor retornado e o array de dependência no slot do componente na árvore interna do React. Na próxima renderização ele percorre o novo array de dependência contra o armazenado, comparando elemento por elemento com Object.is; qualquer discrepância re-executa a função e substitui ambos. Isso torna o array de dependência em si sujeito a igualdade referencial: uma dependência de objeto que recebe uma nova referência a cada renderização derrota a memoização de dentro para fora, que é por isso que a estabilização tende a cair em cascata para cima através de um componente.

memo executa a mesma comparação Object.is em cada chave do objeto props. Aceita um segundo argumento opcional, uma função recebendo as props antigas e novas para que você possa definir igualdade você mesmo, e a documentação do React está certa que você quase nunca precisará disso.

A aresta mais aguçada é que a comparação é tudo-ou-nada por componente: uma prop instável entre dez estáveis re-renderiza o filho toda vez, então um invólucro memo é apenas tão bom quanto a prop menos estável que recebe, e um invólucro que nunca funciona aparece no Profiler como uma subárvore que continua re-renderizando.

Mantenha duas fronteiras em mente. useCallback(fn, deps) é literalmente useMemo(() => fn, deps), então qualquer coisa verdadeira para um é verdadeira para o outro. E useMemo é uma dica de performance em vez de uma garantia semântica: React se reserva o direito de jogar valores em cache fora, por exemplo quando um componente suspende durante sua montagem inicial, ou em desenvolvimento sempre que você edita o arquivo, então o código deve ficar correto se a função re-executar. Armazene coisas que devem sobreviver a re-renderizações sem exceção em state ou uma ref. O mesmo raciocínio de identidade estável se aplica a context valores de provedor, que o deep dive daquele capítulo já cobre.

JunoReferências estáveis pulam trabalho React re-executa o corpo inteiro de um componente em cada renderização, então qualquer cálculo lento ali dentro executa novamente e novamente. useMemo deixa React se lembrar da resposta e reutilizá-la até que as entradas mudem. memo faz algo similar para um componente inteiro: se suas props são as mesmas de última vez, React pula renderizá-lo.

A parte complicada é que dois objetos ou funções que parecem idênticos ainda contam como diferentes em JavaScript, que é por isso que essas ferramentas às vezes precisam uma da outra. E antes de usar qualquer uma delas, certifique-se de que algo é de fato lento.

JunoReferências estáveis pulam trabalho Envolva um cálculo custoso em useMemo com suas entradas reais como dependências, e envolva um filho custoso em memo para pulá-lo quando props não mudaram.

Então se lembre de que objetos e funções criadas em um corpo de componente são novas referências a cada renderização, então um filho memo recebendo-as re-renderiza mesmo assim: estabilize objetos com useMemo e funções com useCallback. Definidores de estado já são estáveis.

Meça com uma CPU acelerada primeiro; a maioria das re-renderizações não custa nada que valha a pena otimizar.

JunoReferências estáveis pulam trabalho Tudo aqui se reduz a Object.is: arrays de dependência são comparados elemento por elemento, memo compara props de forma rasa, e uma referência instável em qualquer lugar quebra a corrente. useCallback(fn, deps) é useMemo(() => fn, deps), e useMemo é uma dica que React pode descartar, então mantenha a correção independente do cache.

O React Compiler agora automatiza essa análise em tempo de compilação; os hooks manuais importam para código existente e para entender o que o compilador está fazendo em seu nome.

Próximo: Code splitting, onde a outra alavanca, o tamanho do bundle, tira sua vez.