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。

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

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

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

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

相关推荐
陈随易4 分钟前
副业搞钱点子之 Umami 统计如何赚钱
前端·后端·程序员
美狐美颜sdk27 分钟前
直播APP开发中的视频帧处理:美颜SDK接入需要注意哪些细节?
大数据·前端·人工智能·音视频·美颜sdk
随风一样自由36 分钟前
【React+TS71007】Next.js “use client” 误用导致 TS71007:客户端/服务端引入问题的完整排查与修复
前端·javascript·react.js
计算机魔术师1 小时前
1.4万亿没人接,OpenAI自己把价砍了1500亿
前端
非凡ghost1 小时前
Photo Supreme:专业高效的照片收藏管理专家
服务器·前端·人工智能·电脑·软件需求
zhangminghuan1 小时前
前端动态路由实现方案(Vue3 + VueRouter4)|后台权限路由实战
前端
一切皆是因缘际会1 小时前
大模型应用
前端·人工智能
SCandL1521 小时前
【无标题】
前端
你挚爱的强哥2 小时前
如何设置 Chrome 打开默认页面不要是360的主页https://hao.360.com/?src=lm&ls=n6205869099
前端·chrome·360首页劫持
总要学点什么2 小时前
第75篇:右键菜单控件——把一张会做事的菜单钉在鼠标落下的那点
前端·信息可视化·echarts·cesium·空间分析