React: Otimização de desempenho: useMemo e useCallback

Última atualização: 2026-10-26

1. O que você vai aprender



2. A história de uma página de filtro de pesquisa

A relação entre os três componentes da otimização do desempenho

100%
flowchart TD
    A[Parent Component Rendering] --> B{Props It's changed?}
    B -->|Yes| C[React.memo Intercept?]
    B -->|No| D[Skip subcomponents ✅]
    C -->|A rough comparison is roughly equal| D
    C -->|Shallow Comparison Inequality| E{Function/The object is new.?}
    E -->|Yes| F[useCallback/useMemo]
    F --> G[Stable Citation → memo Effective ✅]
    E -->|No| D
    
    style F fill:#e8f5e9,stroke:#2e7d32
    style D fill:#c8e6c9,stroke:#2e7d32
    style G fill:#c8e6c9,stroke:#2e7d32

(1) Problema: Recálculo a cada entrada

Alice criou uma página de busca de produtos com 10.000 itens para filtrar:

JSX
function ShopPage() {
  const [products] = useState(generate10000Products())
  const [search, setSearch] = useState('')
  const [sortBy, setSortBy] = useState('name')

  // ❌ Question:Re-filter after each input 10,000 Items
  const filtered = products
    .filter(p => p.name.includes(search))
    .sort((a, b) => a[sortBy] > b[sortBy] ? 1 : -1)

  return (
    <div>
      <input value={search} onChange={e => setSearch(e.target.value)} />
      <select value={sortBy} onChange={e => setSortBy(e.target.value)}>
        <option value="name">By Name</option>
        <option value="price">By Price</option>
      </select>
      <ProductList products={filtered} />
    </div>
  )
}
▶ Experimente

Problema: Sempre que a entrada do teclado (setSearch) é inserida, isso faz com que filter e sort sejam executados novamente — 10.000 registros precisam ser filtrados a cada vez, o que causa lentidão na interface.

(2) A solução useMemo

JSX
function ShopPage() {
  const [products] = useState(generate10000Products())
  const [search, setSearch] = useState('')
  const [sortBy, setSortBy] = useState('name')

  // ✅ useMemo:Only at search or  sortBy Recalculate only when there is a change
  const filtered = useMemo(() => {
    console.log('Refilter...(Only when the search terms or sorting criteria change)')
    return products
      .filter(p => p.name.includes(search))
      .sort((a, b) => a[sortBy] > b[sortBy] ? 1 : -1)
  }, [search, sortBy, products])

  return (
    <div>
      <input value={search} onChange={e => setSearch(e.target.value)} />
      <select value={sortBy} onChange={e => setSortBy(e.target.value)}>
        <option value="name">By Name</option>
        <option value="price">By Price</option>
      </select>
      <ProductList products={filtered} />
    </div>
  )
}
▶ Experimente

Benefícios: A filtragem só é executada novamente quando a entrada muda para search ou sortBy; outras mudanças de estado (como a reexibição do componente pai) não acionam a filtragem, resultando em uma melhoria de desempenho de 10 a 100 vezes.



3. useMemo: Armazenamento em cache de resultados de cálculos

Hook O que é preciso lembrar Valor de retorno Quando as dependências mudam
useMemo Resultado do cálculo Valor armazenado em cache Recalcular
useCallback Referências de funções Funções armazenadas em cache Criação de novas funções
React.memo Resultado renderizado de todo o componente Ignorar nova renderização Renderizar novamente quando os props mudarem

(1) Sintaxe básica

JSX
const memoizedValue = useMemo(() => {
  return Costly Computations()
}, [Dependency1, Dependency2])
▶ Experimente
Seção Descrição
() => Computation Uma função que precisa ser armazenada em cache e retorna o resultado do cálculo
[Dependencies] Recalcular quando as dependências mudarem
memoizedValue Resultados em cache (retorna diretamente o resultado anterior se as dependências não tiverem mudado)

(2) Cenários aplicáveis

JSX
// Scene 1:Data Conversion(Filter、Sort、Grouping)
const activeUsers = useMemo(
  () => users.filter(u => u.isActive).sort((a, b) => a.name.localeCompare(b.name)),
  [users]
)

// Scene 2:Format(Numbers、Date、String Concatenation)
const formattedPrice = useMemo(
  () => new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' }).format(price),
  [price]
)

// Scene 3:Derived State(Calculating New Data from Existing Data)
const statistics = useMemo(() => ({
  total: orders.length,
  paid: orders.filter(o => o.status === 'paid').length,
  amount: orders.reduce((sum, o) => sum + o.amount, 0),
  avgAmount: orders.length ? orders.reduce((sum, o) => sum + o.amount, 0) / orders.length : 0
}), [orders])
▶ Experimente

Saída:

TEXT
...

▶ Exemplo: Comparação de desempenho

JSX
// ============================================
// Example:useMemo vs no useMemo Performance Comparison
// ============================================

function ExpensiveCalc({ num, unrelated }) {
  // ❌ None useMemo:Executed on every render
  const slowResult1 = slowFibonacci(num)

  // ✅ With useMemo: num If it hasn't changed, don't recalculate it.
  const slowResult2 = useMemo(() => slowFibonacci(num), [num])

  return (
    <div>
      <p>num={num} The Fibonacci sequence</p>
      <p>Without useMemo: {slowResult1}(Rendering calculations each time)</p>
      <p>With useMemo: {slowResult2}(Only at num Calculate when changes occur)</p>
      <p>Unrelated Status:{unrelated}(Click the button to change this value)</p>
    </div>
  )
}

function slowFibonacci(n) {
  if (n <= 1) return n
  let a = 0, b = 1, c
  for (let i = 2; i <= n; i++) { c = a + b; a = b; b = c }
  console.log('fibonacci Calculation!n=' + n)
  return b
}
// Click to Make a Difference unrelated → without useMemo recalculates,with useMemo skips
▶ Experimente

Saída:

TEXT
Fibonacci(n): sem useMemo recalcula a cada renderização, com useMemo pula quando apenas estados não relacionados mudam. Console: "fibonacci calculated! n=X"


4. useCallback: Lembrando-se das referências a funções

(1) Sintaxe básica

JSX
const memoizedFn = useCallback(() => {
  doSomething(a, b)
}, [a, b])
▶ Experimente

useCallback(fn, deps) é equivalente a useMemo(() => fn, deps) — o que é armazenado é a própria função, e não o resultado da função.

(2) Por que o useCallback é necessário?

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

  // ❌ Every time Parent Rendering,handleClick These are all new functions
  // resulting in Child 's  React.memo Failure
  const handleClick = () => {
    console.log('Clicked')
  }

  // ✅ useCallback:handleClick Citation unchanged
  // Child 's  React.memo Works properly(Skip Re-rendering)
  const handleClick = useCallback(() => {
    console.log('Clicked')
  }, [])  // Empty Dependency:This function will never change.
}
▶ Experimente

(3) React.memo: Evitando que componentes filhos sejam renderizados novamente

▶ Experimente

5. Quando usar e quando não usar

(1) Quando usar

Cenário Gancho Motivo
Filtragem/Classificação de Big Data useMemo Evitar percorrer grandes quantidades de dados a cada renderização
Cálculos complexos (Matemática/Formatação) useMemo Não é preciso recalcular se o resultado for o mesmo
Passagem de propriedades para um componente filho do React.memo useCallback Manutenção da estabilidade das referências às funções para que o React.memo funcione
Funções nas dependências do useEffect useCallback Como evitar que o useEffect seja executado várias vezes devido a alterações nas referências da função

(2) Situações em que não é necessário

JSX
// ❌ Not necessary:Simple Calculations
const doubled = useMemo(() => count * 2, [count])
// ✅ Write it directly:count * 2 It's already pretty fast as it is
const doubled = count * 2

// ❌ Not necessary:Handle events without passing them to child components
const handleClick = useCallback(() => {
  console.log('Click')
}, [])
// ✅ Just write it down.
const handleClick = () => console.log('Click')

// ❌ Not necessary:The child component isn't being used. React.memo
// useCallback It's been packaged, but no one is buying it."Stable Citation"
▶ Experimente

(3) O custo do abuso

JSX
// useMemo and  useCallback It involves expenses(Relies heavily on arrays)
// Abuse = Introducing Additional Comparison Logic,Slower than if it weren't optimized

// Rule of thumb:
// 1. First, let's write the version without optimization.
// 2. Feeling some lag(fps < 60)Use again
// 3. use  React DevTools Profiler Measurement
▶ Experimente

6. Exemplo completo: Lista de mensagens em um aplicativo de bate-papo

▶ Expandir exemplo executável (112 linhas)
▶ Experimente

Saída:

TEXT
... ('fibonacci Calculation!n=' + n)

Resultado esperado: Uma lista de bate-papo com 100 mensagens. Ao clicar em “Aumentar contagem de mensagens não lidas”, a lista de mensagens não será atualizada (não haverá saída no console) devido a useCallback e React.memo; apenas a contagem será alterada.


▶ Exemplo 2: o useCallback impede que o useEffect seja executado várias vezes

▶ Experimente

Saída:

TEXT
Caixa de busca + paginação. Digitar "React" → 3 resultados ("Results React - Page 1" etc.). useCallback evita o loop infinito no useEffect. Botões Prev/Next.

Saída:

TEXT
Subtítulo: "🔍 Search(useCallback Demo)". Exibe: "🔍 Search(useCallback Demo)". Estado: query (setter: setQuery), results (setter: setResults), page (setter: setPage). Botão: setPage(p => p + 1)}>Next Page. Entrada: Enter a search term.... Lista: {r.title}. useEffect gerencia efeitos colaterais. useCallback memoiza a referência do handler. Busca assíncrona de dados / estados de carregamento

▶ Exemplo 3: Utilização de useMemo para implementar cálculos de listas virtuais

JSX
function VirtualList({ items, itemHeight = 40, visibleHeight = 400 }) {
  const [scrollTop, setScrollTop] = useState(0)

  const visibleCount = Math.ceil(visibleHeight / itemHeight)
  const totalHeight = useMemo(() => items.length * itemHeight, [items.length, itemHeight])

  const { startIndex, endIndex, offsetY } = useMemo(() => {
    const start = Math.floor(scrollTop / itemHeight)
    const end = Math.min(start + visibleCount + 1, items.length)
    return { startIndex: start, endIndex: end, offsetY: start * itemHeight }
  }, [scrollTop, itemHeight, visibleCount, items.length])

  const visibleItems = useMemo(() => items.slice(startIndex, endIndex), [items, startIndex, endIndex])

  return (
    <div onScroll={e => setScrollTop(e.currentTarget.scrollTop)}
      style={{ height: visibleHeight, overflowY: 'auto', border: '1px solid #ddd', borderRadius: 4 }}>
      <div style={{ height: totalHeight, position: 'relative' }}>
        <div style={{ position: 'absolute', top: offsetY, width: '100%' }}>
          {visibleItems.map((item, i) => (
            <div key={startIndex + i} style={{ height: itemHeight, padding: '0 12px', display: 'flex', alignItems: 'center', borderBottom: '1px solid #f0f0f0' }}>
              Row {startIndex + i}: {item}
            </div>
          ))}
        </div>
      </div>
    </div>
  )
}
▶ Experimente

Saída:

TEXT
Rolagem virtual: renderiza apenas as linhas visíveis (~10 de 1000+). Rolagem → as linhas são trocadas. Altura total = items × 40px. useMemo calcula o intervalo visível.

Saída:

TEXT
Exibe: "items.slice(startIndex, endIndex), [items, startIndex, endIn". Estado: scrollTop (setter: setScrollTop). useMemo otimiza o cálculo

▶ Exemplo 4: Otimização de listas com React.memo + useCallback

▶ Experimente

Saída:

TEXT
Lista de produtos (Keyboard $79, Mouse $49, Monitor $399, Headphones $129) com filtro. "Remove" exclui o item. React.memo + useCallback = apenas os itens alterados são renderizados novamente.

Saída:

TEXT
Renderização: ...

▶ Exemplo 5: Quando não usar useMemo — Comparação de antipadrões

JSX
function BadExample({ count }) {
  // ❌ Simple calculations are not required useMemo——Create memo Expenses > The Calculation Itself
  const doubled = useMemo(() => count * 2, [count])
  // ✅ Direct Calculation
  const doubledOk = count * 2

  // ❌ Each render creates a new object as useMemo Dependency
  const config = useMemo(() => ({ count }), [count])
  const badResult = useMemo(() => config.count * 3, [config]) // config A new reference each time!
  // ✅ Using Primitive Types Directly as Dependencies
  const goodResult = useMemo(() => count * 3, [count])

  return <p>Doubled: {doubledOk}, Tripled: {goodResult}</p>
}

function GoodExample({ items, query }) {
  // ✅ Filter/Sorting large amounts of data useMemo
  const filtered = useMemo(() => {
    return items.filter(item =>
      item.name.toLowerCase().includes(query.toLowerCase())
    ).sort((a, b) => a.name.localeCompare(b.name))
  }, [items, query])

  // ✅ Complex objects passed to child components use useMemo
  const chartData = useMemo(() => ({
    labels: filtered.map(i => i.name),
    values: filtered.map(i => i.price),
  }), [filtered])

  return (
    <div>
      <p>Found: {filtered.length} items</p>
      <Chart data={chartData} />
    </div>
  )
}
▶ Experimente

Saída:

TEXT
❌ useMemo para count*2 (o custo é maior que o próprio cálculo). ❌ Novo objeto como dependência quebra o memo. ✅ useMemo para filtros/ordenações de grande volume e para objetos de dados de gráficos derivados.

❓ Perguntas Frequentes

P useMemo e useCallback sempre melhoram o desempenho?
R Não necessariamente. Eles trazem sua própria sobrecarga — comparam a matriz dependencies a cada renderização. Usá-los em excesso pode, na verdade, tornar o sistema mais lento. Recomendação oficial do React: comece escrevendo a versão não otimizada e só otimize quando perceber lentidão. Regra geral: use-os apenas para cálculos passados a um componente filho do React.memo ou aqueles com uma sobrecarga superior a 1 ms.
P Qual é a diferença entre useMemo e uma variável comum?
R useMemo possui “memória” — se as dependências não tiverem mudado, ela usa o valor armazenado em cache diretamente, sem reavaliar a expressão. Uma variável comum é reavaliada a cada renderização. No entanto, useMemo precisa comparar a matriz de dependências durante a renderização, de modo que “cálculos simples ficam, na verdade, mais lentos com useMemo”.
P Quando se deve usar useCallback para preservar uma referência a uma função?
R Você só precisa usá-lo quando ambas as condições a seguir forem atendidas: ① A função é passada como um prop para um componente filho do React.memo; ② A função está no array de dependências de um hook useEffect. Se nenhuma dessas condições se aplicar, você pode simplesmente escrever a função diretamente; useCallback é desnecessário.
P Qual é a diferença entre useMemo e useCallback?
R useMemo(() => value, deps) armazena em cache o resultado (valor) de um cálculo, enquanto useCallback(() => fn, deps) armazena em cache uma referência a uma função. Na verdade, useCallback(fn, deps) é equivalente a useMemo(() => fn, deps). Use useCallback quando precisar armazenar em cache uma função passada para um componente filho; use useMemo quando precisar armazenar em cache o resultado de um cálculo.

📖 Resumo


📝 Exercícios

  1. Problema Básico (Dificuldade ⭐): Crie um componente PrimeCalculator que receba um número n como entrada e calcule o enésimo número primo. Use useMemo para armazenar o resultado em cache, de modo que ele não seja recalculado toda vez que um novo valor for inserido.
  2. Exercício avançado (Dificuldade ⭐⭐): Crie um componente ProductTable que contenha 500 registros de produtos e permita a filtragem por categoria e a ordenação por preço. Use useMemo para otimizar a lógica de filtragem e ordenação, e use useCallback em combinação com React.memo para otimizar a renderização da lista.
  3. Desafio (Dificuldade: ⭐⭐⭐): Crie um componente DataDashboard que agregue 10.000 registros de pedidos em três dimensões diferentes (tempo, região e categoria) e use useMemo para implementar o armazenamento em cache hierárquico (de modo que apenas as camadas relevantes sejam recalculadas quando as dimensões mudarem).
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%