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

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

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

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

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

相关推荐
心在飞扬1 小时前
ReRank重排序提升RAG系统效果
前端·后端
心在飞扬1 小时前
RAPTOR 递归文档树优化策略
前端·后端
前端Hardy1 小时前
别再无脑用 `JSON.parse()` 了!这个安全漏洞你可能每天都在触发
前端·javascript·vue.js
前端Hardy2 小时前
别再让 `console.log` 上线了!它正在悄悄拖垮你的生产系统
前端·javascript·vue.js
青青家的小灰灰2 小时前
从入门到精通:Vue3 ref vs reactive 最佳实践与底层原理
前端·vue.js·面试
OpenTiny社区2 小时前
我的新同事是个AI:支持skill后,它用TinyVue搭项目还挺溜!
前端·vue.js·ai编程
是你的小橘呀2 小时前
TypeScript在React项目中的实战应用指南
react.js
心在飞扬2 小时前
MultiVector 多向量检索
前端·后端
用户39051332192882 小时前
async 函数返回的 Promise 状态何时变为 resolved
前端
李剑一2 小时前
大屏天气展示太普通?视觉升级!用 Canvas 做动态天气遮罩,雷阵雨效果直接封神
前端·vue.js·canvas