iChengHub
首页技术博客工具分类效率导航关于
EN
提交 / 许愿
iChengHub
皖ICP备2025085990号-1
© 2026 iChengHub 热荐工坊 · 和光工作室 · 保留所有权利
© 2026 iChengHub 热荐工坊 · 和光工作室 · 保留所有权利
皖ICP备2025085990号-1
首页/博客/React 中,useMemo 和 useCallback 的区别?什么时候使用?

React 中,useMemo 和 useCallback 的区别?什么时候使用?

React2026-09-031

精炼回答

useMemo 和 useCallback 都是 React 中用于性能优化的 Hook,但它们缓存的对象不同:

  • useMemo 缓存的是计算结果。
  • useCallback 缓存的是函数引用。

具体来说,useMemo 会执行传入的计算函数并缓存其返回值。在通常情况下,只要依赖项没有变化,React 就会复用之前缓存的结果;当依赖项发生变化时,则重新执行计算函数。

例如:

const activeItems = useMemo(
  () => items.filter(item => item.active),
  [items]
);
JavaScript

这样可以避免在依赖项未变化时重复执行相同的过滤计算。

useCallback 则用于缓存函数引用。在通常情况下,只要依赖项没有变化,React 就会返回之前缓存的函数引用。

例如:

const handleSelect = useCallback((id) => {
  setSelectedId(id);
}, []);
JavaScript

它常用于以下场景:

  • 将函数作为 prop 传给使用 React.memo 包装的子组件。
  • 某个函数需要作为其他 Hook 的依赖项。
  • 某些依赖函数引用稳定性的第三方组件或自定义 Hook。

简单来说:

Hook缓存内容常见用途
useMemo计算结果避免重复执行昂贵计算、稳定对象或数组引用
useCallback函数引用稳定回调函数引用、配合 React.memo、作为其他 Hook 的依赖

需要注意的是,useMemo 和 useCallback 本身也存在依赖比较、缓存维护等开销,因此不应该机械地给所有计算和函数都加上记忆化。

优先在计算成本较高、需要稳定引用、与 React.memo 配合,或者函数需要作为其他 Hook 依赖项等明确场景中使用。对于性能敏感场景,最好使用 React DevTools Profiler 等工具验证实际收益。

扩展分析

详细解释

掌握这两个 Hook 的关键,在于理解它们解决的是两个不同的问题:

  • useMemo 主要解决重复计算问题。
  • useCallback 主要解决函数引用不稳定问题。

在组件后续渲染时,React 会使用 Object.is 将依赖数组中的每一项与上一次渲染时对应的依赖项进行比较。

只要其中任意依赖发生变化:

  • useMemo 会重新执行计算函数,并缓存新的计算结果。
  • useCallback 会返回当前渲染中传入的新函数引用。

因此,更准确的描述不是“React 对依赖数组进行浅比较”,而是:

React 会逐项使用 Object.is 比较依赖。

对于基本类型,例如:

1
'hello'
true
JavaScript

React 比较的是值。

对于对象、数组和函数,如果每次渲染都重新创建,即使内容完全相同,其引用也通常不同,因此会被判断为依赖发生了变化。

例如:

const options = {
  sort: 'price'
};
JavaScript

如果这段代码直接写在组件函数内部,那么每次渲染都会创建一个新的对象。

因此:

useMemo(() => {
  // ...
}, [options]);
JavaScript

中的 options 很可能每次渲染都会发生变化,从而导致 useMemo 失去预期的缓存效果。

可以把依赖变化过程理解为:

组件重新渲染
    ↓
React 使用 Object.is
逐项比较依赖
   ↙      ↘
无变化      有变化
  ↓          ↓
复用缓存      重新执行 / 返回新引用
               ↓
             更新缓存
   ↘          ↙
    组件完成渲染

需要注意的是,useMemo 和 useCallback 都属于性能优化手段,而不是业务语义保证。

因此,不应该依赖它们来保证程序逻辑正确性。即使缓存被丢弃,组件逻辑也应该仍然能够正确工作。

useMemo:缓存计算结果

useMemo 的性能收益主要来自避免重复执行昂贵计算。

例如:

const result = useMemo(() => {
  return expensiveCalculation(data);
}, [data]);
JavaScript

当 data 没有变化时,React 通常会复用之前的 result,而不是再次执行 expensiveCalculation。

但使用 useMemo 是否值得,取决于:

重新计算的成本
        VS
依赖比较 + 缓存维护的成本

如果只是:

const total = price * count;
JavaScript

或者:

const names = users.map(user => user.name);
JavaScript

而数据规模又非常小,那么使用 useMemo 往往没有明显收益,反而增加了代码复杂度。

但如果是:

  • 大量数据过滤。
  • 多条件排序。
  • 深度递归计算。
  • 复杂数据转换。
  • 大型图表数据预处理。

那么 useMemo 可能具有实际价值。

useCallback:缓存函数引用

JavaScript 中,函数也是对象。

例如:

function Parent() {
  const handleClick = () => {
    console.log('clicked');
  };

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

每次 Parent 重新渲染时:

const handleClick = () => {
  console.log('clicked');
};
JavaScript

都会创建一个新的函数对象。

也就是说:

previousHandleClick === currentHandleClick
JavaScript

结果通常为:

false
JavaScript

如果 Child 使用了 React.memo:

const Child = React.memo(function Child({ onClick }) {
  return <button onClick={onClick}>Click</button>;
});
JavaScript

父组件每次创建新的 handleClick,都会导致 onClick 这个 prop 被判断为发生了变化。

这并不意味着 React.memo “失效”,而是:

由于函数 prop 的引用发生了变化,React.memo 无法仅依靠 props 相等来跳过这一次子组件渲染。

此时可以使用:

const handleClick = useCallback(() => {
  console.log('clicked');
}, []);
JavaScript

只要依赖没有变化,React 通常会保持函数引用稳定。

实践应用

场景一:复杂商品筛选

在电商场景中,经常需要根据多个条件筛选大量商品:

const filteredProducts = useMemo(() => {
  return products
    .filter(product => {
      return (
        product.price >= priceRange[0] &&
        product.price <= priceRange[1] &&
        selectedBrands.includes(product.brand) &&
        product.rating >= minRating
      );
    })
    .sort((a, b) => a.price - b.price);
}, [products, priceRange, selectedBrands, minRating]);
JavaScript

假设 products 包含几千条数据,每次过滤还包含品牌判断、价格区间判断、评分判断和排序,那么这类计算就可能值得使用 useMemo。

不过需要特别注意 priceRange:

const priceRange = [minPrice, maxPrice];
JavaScript

如果父组件在每次渲染时都创建一个新的数组,那么即使:

minPrice
maxPrice
JavaScript

完全没有变化,priceRange 的引用仍然可能不同,从而导致 useMemo 重新执行。

可以考虑直接拆成基本类型:

const filteredProducts = useMemo(() => {
  return products
    .filter(product => {
      return (
        product.price >= minPrice &&
        product.price <= maxPrice &&
        selectedBrands.includes(product.brand) &&
        product.rating >= minRating
      );
    })
    .sort((a, b) => a.price - b.price);
}, [products, minPrice, maxPrice, selectedBrands, minRating]);
JavaScript

或者确保 priceRange 本身的引用稳定。

场景二:稳定对象引用

另一个常见场景是向子组件传递配置对象。

例如:

const chartConfig = useMemo(() => ({
  type: 'line',
  data: salesData,
  options: {
    responsive: true,
    scales: {
      y: {
        beginAtZero: true
      }
    }
  }
}), [salesData]);
JavaScript

如果这个配置对象需要传给一个依赖引用稳定性的组件:

<Chart config={chartConfig} />
JavaScript

使用 useMemo 可以避免父组件每次渲染都重新创建一个新的配置对象。

但这里仍然要注意:

只有当对象引用稳定确实能够避免额外工作时,useMemo 才有意义。

如果 Chart 本身每次都会重新渲染,而且配置对象创建成本极低,那么这个优化可能没有实际收益。

场景三:稳定事件处理函数

useCallback 最常见的用途之一,是稳定传递给子组件的事件处理函数。

例如:

const handleProductClick = useCallback((productId) => {
  setSelectedProduct(productId);
  analytics.track('product_click', { productId });
}, [analytics]);
JavaScript

然后:

products.map(product => (
  <ProductCard
    key={product.id}
    product={product}
    onClick={handleProductClick}
  />
));
JavaScript

如果 ProductCard 使用:

const ProductCard = React.memo(function ProductCard({
  product,
  onClick
}) {
  // ...
});
JavaScript

那么稳定的 onClick 引用可以减少因为函数引用变化导致的额外渲染。

这里需要特别注意依赖数组。

如果:

analytics
JavaScript

来自组件作用域中的 prop、state、context 或其他 reactive value,那么应该将其加入依赖数组:

[analytics]
JavaScript

而:

setSelectedProduct
JavaScript

如果来自:

const [selectedProduct, setSelectedProduct] = useState(null);
JavaScript

React 保证 state setter 的函数引用稳定,因此通常可以省略。

如果 analytics 是模块级导入的稳定单例:

import analytics from './analytics';
JavaScript

则它不是组件内部的 reactive dependency,此时可以写:

const handleProductClick = useCallback((productId) => {
  setSelectedProduct(productId);
  analytics.track('product_click', { productId });
}, []);
JavaScript

实际项目中应结合 ESLint 的 react-hooks/exhaustive-deps 规则检查依赖,而不是凭感觉手工删除依赖。

常见误区

误区一:所有函数都应该使用 useCallback

错误思路:

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

然后组件里所有函数都这样写。

实际上,如果:

  • 这个函数没有传给记忆化子组件。
  • 它没有作为其他 Hook 的依赖。
  • 创建函数本身成本很低。
  • 子组件本身渲染也非常快。

那么使用 useCallback 往往没有实际价值。

误区二:所有计算都应该使用 useMemo

例如:

const fullName = useMemo(() => {
  return `${firstName} ${lastName}`;
}, [firstName, lastName]);
JavaScript

这种计算极其简单,一般没有必要使用 useMemo。

直接:

const fullName = `${firstName} ${lastName}`;
JavaScript

通常更加清晰。

误区三:对象内容一样,React 就认为依赖没变

例如:

const filters = {
  keyword,
  category
};
JavaScript

即使两次渲染时:

filters.keyword
filters.category
JavaScript

的值完全相同,只要创建了新的对象:

previousFilters !== currentFilters
JavaScript

依赖就会被判断为发生变化。

因此不要只关注对象内容,还要关注引用是否稳定。

误区四:useCallback 一定能减少子组件渲染

并不是。

假设:

const handleClick = useCallback(() => {
  // ...
}, []);
JavaScript

虽然 handleClick 稳定了,但如果子组件还有其他 prop 每次都在变化:

<Child
  onClick={handleClick}
  options={{ theme: 'dark' }}
/>
JavaScript

这里:

options={{ theme: 'dark' }}
JavaScript

每次渲染都会创建新对象。

因此即使 onClick 没变化,React.memo 仍然可能因为 options 变化而重新渲染。

误区五:React.memo 可以保证组件不重新渲染

不能。

React.memo 是一种性能优化机制。

默认情况下,它会比较组件的新旧 props。如果 props 被判断为相同,React 通常可以跳过该组件由父组件更新触发的重新渲染。

但组件自身的:

  • state 变化。
  • context 变化。
  • 其他 React 内部调度行为。

仍然可能导致组件重新渲染。

因此不要把 React.memo 当作组件“不重新渲染”的语义保证。

useMemo 与 useCallback 的关系

从概念上看:

useCallback(fn, dependencies)
JavaScript

可以理解为接近:

useMemo(() => fn, dependencies)
JavaScript

区别在于 API 的表达方式不同。

useMemo:

const value = useMemo(() => {
  return calculateSomething();
}, [dependencies]);
JavaScript

缓存:

calculateSomething() 的返回值

而 useCallback:

const callback = useCallback(() => {
  doSomething();
}, [dependencies]);
JavaScript

缓存:

函数引用

因此:

useMemo
    ↓
关注「值」

useCallback
    ↓
关注「函数」

这是理解两者区别最简单的方法。

如何判断是否应该使用

在实际项目中,可以按照下面的思路判断。

是否应该使用 useMemo

可以考虑使用:

  • 计算明显昂贵。
  • 数据量较大。
  • 计算会频繁重复。
  • 返回对象或数组,并且引用稳定性会影响下游组件。
  • Profiler 显示该计算确实存在明显耗时。

通常不需要使用:

  • 简单字符串拼接。
  • 简单数学运算。
  • 小数组的轻量处理。
  • 缓存结果不会影响任何性能敏感组件。

是否应该使用 useCallback

可以考虑使用:

  • 函数作为 prop 传给 React.memo 子组件。
  • 函数是其他 Hook 的依赖项。
  • 第三方组件依赖 callback 引用稳定。
  • 自定义 Hook 明确要求稳定函数引用。
  • Profiler 证明函数引用变化导致了明显的额外渲染。

通常不需要使用:

  • 函数只在当前组件内部使用。
  • 子组件没有做 memoization。
  • 子组件渲染成本非常低。
  • callback 每次依赖都会变化,导致缓存几乎无法复用。

性能分析比盲目优化更重要

很多时候,开发者看到:

filter
map
sort
function
JavaScript

就下意识加入:

useMemo
useCallback
JavaScript

这种做法并不一定正确。

更合理的优化流程是:

先实现正确、清晰的代码
        ↓
使用性能分析工具
        ↓
定位真实瓶颈
        ↓
针对问题进行优化
        ↓
再次测量
        ↓
确认优化是否有效

React DevTools 的 Profiler 可以查看:

  • 哪些组件发生了重新渲染。
  • 每次渲染耗时。
  • 哪些更新触发了组件渲染。

Chrome DevTools 的 Performance 面板还可以观察:

  • JavaScript 执行时间。
  • Main Thread 阻塞。
  • 长任务。
  • 浏览器渲染过程。
  • 火焰图。

这些工具通常比盲目增加 useMemo 和 useCallback 更有价值。

React Compiler 对手动记忆化的影响

随着 React 并发特性、Server Components 以及 React Compiler 的发展,React 的性能优化策略也在持续演进。

在启用了 React Compiler 的项目中,编译器可以自动完成许多原本需要开发者通过:

React.memo
useMemo
useCallback

手动实现的记忆化优化。

因此,在使用 React Compiler 的新项目中,不应该机械地为所有值和函数增加 useMemo 或 useCallback。

更合理的做法是综合考虑:

  • 项目是否启用了 React Compiler。
  • 当前组件是否真的存在重复计算。
  • 引用变化是否真的导致昂贵的子组件重新渲染。
  • 是否存在需要手动控制引用稳定性的特殊场景。
  • Profiler 的实际性能数据。

即使如此,理解 useMemo、useCallback 和 React.memo 仍然非常重要。

因为它们背后反映的是 React 性能优化中的几个核心概念:

引用稳定性
    +
依赖关系
    +
组件重新渲染
    +
计算成本

这些概念依然是理解 React 渲染机制和性能问题的重要基础。

总结

useMemo 和 useCallback 最核心的区别可以概括为:

useMemo
  ↓
缓存计算结果
  ↓
减少重复计算

useCallback
  ↓
缓存函数引用
  ↓
减少不必要的引用变化

可以记住下面这个判断原则:

不要因为“可以使用”就使用,而应该因为存在明确的性能或引用稳定性需求才使用。

对于 useMemo:

重点看计算成本

对于 useCallback:

重点看函数引用是否影响其他组件或 Hook

而对于任何性能优化:

先测量
再优化
最后再次验证

通常比盲目添加缓存更可靠。

最后更新于·2026-09-03

←回到列表除了 Redis 实现分布式锁,还有哪些方案可以实现分布式锁?各有什么优缺点?→
精炼回答扩展分析详细解释useMemo:缓存计算结果useCallback:缓存函数引用实践应用场景一:复杂商品筛选场景二:稳定对象引用场景三:稳定事件处理函数常见误区误区一:所有函数都应该使用 useCallback误区二:所有计算都应该使用 useMemo误区三:对象内容一样,React 就认为依赖没变误区四:useCallback 一定能减少子组件渲染误区五:React.memo 可以保证组件不重新渲染useMemo 与 useCallback 的关系如何判断是否应该使用是否应该使用 useMemo是否应该使用 useCallback性能分析比盲目优化更重要React Compiler 对手动记忆化的影响总结