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

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

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

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

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

相关推荐
星哥说事3 小时前
跨平台开源笔记神器,用DeepSeek写笔记 , 效率翻倍
前端
喜欢你,还有大家3 小时前
FTP文件传输服务
linux·运维·服务器·前端
该用户已不存在3 小时前
你没有听说过的7个Windows开发必备工具
前端·windows·后端
Bi4 小时前
Dokploy安装和部署项目流程
运维·前端
普通网友4 小时前
前端安全攻防:XSS, CSRF 等防范与检测
前端·安全·xss
携欢4 小时前
PortSwigger靶场之Reflected XSS into attribute with angle brackets HTML-encoded通关秘籍
前端·xss
海海思思4 小时前
Redux Toolkit的前世今生:从繁琐到简洁的状态管理革命
react.js·redux
小爱同学_4 小时前
React知识:useState和useRef的使用
前端·react.js
再学一点就睡4 小时前
双 Token 认证机制:从原理到实践的完整实现
前端·javascript·后端