useCallback 和 useMemo,什么时候用才是有效的?

在最近的项目代码走查中,我发现一个现象:

很多同事几乎 「条件反射式地给所有函数包一层 useCallback ,或者 「看到复杂计算就顺手套个 useMemo

表面上看,这样写好像更「规范」,但实际上很多地方完全没必要,甚至可能导致性能变差。于是我决定写下这篇文章,聊聊 useCallback / useMemo 什么时候用才有效


先复习一下它们的作用

  • useCallback(fn, deps)

    👉 返回一个稳定的函数引用。

    • 常用场景:把函数传给子组件,避免子组件不必要的重渲染。
  • useMemo(factory, deps)

    👉 缓存一个计算结果。

    • 常用场景:某个计算量大、依赖不常变的值。

一句话总结:

它们本身并不会提升性能,只有在「依赖变化频率低 + 使用场景敏感」时才有价值。


常见的误区

1. 「所有函数都要用 useCallback」

很多同学担心函数每次 render 都会重新创建,所以选择「一律用 useCallback 包裹」。

⚠️ 这是个误区。函数创建本身的开销极小,远远小于 React 内部维护依赖数组、闭包和 GC 的开销。

如果这个函数只是组件内部用,不会传递给子组件,那么用 useCallback 完全是 浪费


2. 「计算过程都要用 useMemo」

有人觉得「计算逻辑里有数组 map 或对象处理」就必须用 useMemo,否则会影响性能。

⚠️ 其实大部分计算(比如几百个元素的 map)在 JS 里是毫秒级的,远远小于 React 组件更新的渲染耗时。

真正需要 useMemo 的场景是:

  • 计算量大(比如复杂的排序、深度递归、正则批量匹配)。
  • 依赖不常变(比如固定的配置项、缓存数据)。

什么时候才是「有效使用」?

可以用一句话来判断:

当它们能减少「不必要的子组件重渲染」或「避免大计算的重复执行」时,才是有效的。

✅ 适合用 useCallback 的场景

  • 把函数作为 props 传递给 React.memo 包裹的子组件。
  • 把函数作为 依赖 传递给另一个 useEffect / useMemo

例子:

tsx 复制代码
const Child = React.memo(({ onClick }) => {
  console.log("child render");
  return <button onClick={onClick}>click</button>;
});

export default function App() {
  const [count, setCount] = useState(0);

  // ✅ 用 useCallback 避免 Child 每次都重新渲染
  const handleClick = useCallback(() => setCount(c => c + 1), []);

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

✅ 适合用 useMemo 的场景

  • 渲染时需要执行 复杂计算,而依赖项很少变化。
  • 需要生成一个稳定的 引用值(对象/数组),否则子组件会重复渲染。
tsx 复制代码
const Child = React.memo(({ options }) => {
  console.log("child render");
  return <div>{options.join(",")}</div>;
});

export default function App() {
  const [count, setCount] = useState(0);

  // ✅ 用 useMemo 避免 options 每次 render 都新建数组
  const options = useMemo(() => ["A", "B", "C"], []);

  return (
    <>
      <button onClick={() => setCount(c => c + 1)}>add</button>
      <Child options={options} />
    </>
  );
}

总结:三句话原则

  1. 不要滥用 :不是所有函数/计算都需要 useCallback / useMemo
  2. 关注引用稳定性:只有在「传给子组件」或「作为依赖」时才必要。
  3. 成本大于收益:记住 Hook 本身也有开销,乱用可能比不用更慢。

写在最后

写这篇文章的原因,是因为我看到项目里出现了很多 无效的 useCallback / useMemo

  • 所有函数一律包裹 useCallback
  • 明明是轻量计算却强行 useMemo

这不仅让代码变得冗余,还可能误导新人。希望大家在用它们时,先想一想:

👉 我这样写真的能减少渲染开销吗?

👉 如果去掉它,渲染会不会有性能问题?

如果答案是否定的,那大概率是不需要的。

相关推荐
鹏北海-RemHusband几秒前
从零到一:基于 micro-app 的企业级微前端模板完整实现指南
前端·微服务·架构
LYFlied1 分钟前
AI大时代下前端跨端解决方案的现状与演进路径
前端·人工智能
光影少年5 分钟前
AI 前端 / 高级前端
前端·人工智能·状态模式
一位搞嵌入式的 genius6 分钟前
深入 JavaScript 函数式编程:从基础到实战(含面试题解析)
前端·javascript·函数式
anOnion17 分钟前
构建无障碍组件之Alert Dialog Pattern
前端·html·交互设计
choke23325 分钟前
[特殊字符] Python 文件与路径操作
java·前端·javascript
云飞云共享云桌面27 分钟前
高性能图形工作站的资源如何共享给10个SolidWorks研发设计用
linux·运维·服务器·前端·网络·数据库·人工智能
Deng94520131439 分钟前
Vue + Flask 前后端分离项目实战:从零搭建一个完整博客系统
前端·vue.js·flask
威迪斯特42 分钟前
Flask:轻量级Web框架的技术本质与工程实践
前端·数据库·后端·python·flask·开发框架·核心架构
wuhen_n1 小时前
JavaScript内置数据结构
开发语言·前端·javascript·数据结构