首页 / React 19 入门教程 / useMemo 与 useCallback

React 19 入门教程

useMemo 与 useCallback

本教程共 50 篇 · 第 33 篇 · 更新于 2026-07-29 · 约 7 分钟阅读

ReactuseMemouseCallbackmemoization性能优化

33. useMemo 与 useCallback

本节目标:理解 useMemo 和 useCallback 的作用,掌握引用相等性,知道什么时候该用、什么时候不该用。

先理解引用相等

JavaScript 里,对象和函数每次创建都是新引用:

// 每次渲染都创建新对象
function Component() {
  const options = { limit: 10 }; // 新引用
  // ...
}

// 每次渲染都创建新函数
function Component() {
  const handleClick = () => {}; // 新引用
  // ...
}

{ limit: 10 } === { limit: 10 }false,因为引用不同。

引用相等 vs 值相等

Object.is({}, {}) 返回 false。React 用 Object.is 比较依赖,引用不同就认为变了。

useMemo:缓存计算结果

useMemo 在依赖不变时返回缓存值,避免重复计算:

const cachedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
function TodoList({ todos, filter }) {
  // 只有 todos 或 filter 变化时才重新计算
  const visibleTodos = useMemo(() => {
    return todos.filter(t => t.status === filter);
  }, [todos, filter]);

  return <ul>{visibleTodos.map(...)}</ul>;
}

useMemo 不是银弹

简单计算(字符串拼接、小数组遍历)不需要 useMemo。只有真正耗时的计算才值得缓存。

useCallback:缓存函数引用

useCallback 在依赖不变时返回同一个函数引用:

const cachedFn = useCallback(fn, dependencies);
function ProductPage({ productId }) {
  // 依赖不变,handleSubmit 引用不变
  const handleSubmit = useCallback((orderDetails) => {
    post(`/product/${productId}/buy`, { orderDetails });
  }, [productId]);

  return <ShippingForm onSubmit={handleSubmit} />;
}

什么时候需要 useCallback

单独用 useCallback 没有意义。它的价值在于配合 memo 使用:

import { memo } from 'react';

const ShippingForm = memo(function ShippingForm({ onSubmit }) {
  // 只有 props 变化才重新渲染
  return (/* ... */);
});

没有 useCallback,每次父组件渲染都创建新的 handleSubmitmemo 形同虚设:

// 没有 useCallback:每次都是新函数,memo 失效
function ProductPage() {
  const handleSubmit = (orderDetails) => { /* ... */ };
  return <ShippingForm onSubmit={handleSubmit} />; // 每次都重渲染
}

// 有 useCallback:引用稳定,memo 生效
function ProductPage() {
  const handleSubmit = useCallback((orderDetails) => { /* ... */ }, [deps]);
  return <ShippingForm onSubmit={handleSubmit} />; // props 不变不重渲染
}

不要滥用 useCallback

如果子组件没用 memo,或者函数不传给子组件,useCallback 只是白白增加代码复杂度。

useMemo vs useCallback

useMemouseCallback
缓存什么计算结果(值)函数本身
返回值函数的返回值函数本身
等价写法useMemo(() => fn, deps)useCallback(fn, deps)
// 这两行效果相同
const fn1 = useMemo(() => () => console.log('hi'), []);
const fn2 = useCallback(() => console.log('hi'), []);

什么时候不该用

不需要 useMemo

  • 计算很简单(字符串拼接、数字运算)
  • 结果不是传给 memo 子组件的 props

不需要 useCallback

  • 函数不传给子组件
  • 子组件没有用 memo
  • 函数只在当前组件内部用

确实需要 useCallback

  • 函数传给用了 memo 的子组件
  • 函数作为 effect 的依赖
// 不需要:计算太简单
const fullName = useMemo(() => firstName + ' ' + lastName, [firstName, lastName]);
// 直接算就行
const fullName = firstName + ' ' + lastName;

// 不需要:子组件没 memo
const handleClick = useCallback(() => {}, []);
return <button onClick={handleClick}>点击</button>;

一个实际有用的场景

function Chart({ data, filters }) {
  // 数据转换很耗时,缓存结果
  const processedData = useMemo(() => {
    return data
      .filter(item => item.category === filters.category)
      .map(item => ({ ...item, value: item.value * 100 }))
      .sort((a, b) => b.value - a.value);
  }, [data, filters]);

  // 传给子组件的回调,保持引用稳定
  const handleHover = useCallback((item) => {
    console.log('hover', item.name);
  }, []);

  return <ExpensiveChart data={processedData} onHover={handleHover} />;
}

const ExpensiveChart = memo(function ExpensiveChart({ data, onHover }) {
  // 渲染很耗时,props 不变就不重渲染
});

React Compiler 的影响

React 19 的 React Compiler 能自动 memoization,减少手动 useMemo/useCallback 的场景。但在编译器普及之前,理解这些 Hook 仍然很重要。

先学原理,再谈自动化

即使有 React Compiler,理解引用相等和 memoization 的原理,对排查性能问题仍然至关重要。

本节要点

  • 引用相等:每次渲染创建新对象/函数
  • useMemo 缓存计算结果,useCallback 缓存函数引用
  • 配合 memo 使用才有意义
  • 简单场景不需要,不要过度优化