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

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

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

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

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

相关推荐
To_OC5 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
vipbic6 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy6 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志6 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble7 小时前
c端系统,其实更像一个信息展示平台
前端
李姆斯8 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜9 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司9 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能
kaiking_g10 小时前
vue项目中,静态导入 vs 动态导入 详解
前端·javascript·vue.js
江华森10 小时前
Web 开发基础:HTTP 与 REST
前端·网络协议·http