为什么不要滥用 useMemo 和 useCallback?过度优化反而更卡

为什么不要滥用 useMemo 和 useCallback?过度优化反而更卡

在 React 性能优化中,useMemouseCallback 是最容易被误解和滥用的两个 Hook。

很多开发者一看到"性能优化"就本能地给所有函数、对象、计算结果包一层 useMemo / useCallback,结果却是:代码变复杂了,性能反而变差了

这篇文章会讲清楚:

  • useMemo / useCallback 到底在解决什么问题
  • 为什么"乱用"会导致负优化
  • 什么时候该用,什么时候不该用
  • 正确的性能优化心智模型

一、先搞懂:useMemo 和 useCallback 是干什么的?

useMemo:缓存"值"

ini 复制代码
const computedValue = useMemo(() => {
  return heavyCompute(a, b);
}, [a, b]);

只在 ab 变化时,才重新计算


useCallback:缓存"函数引用"

ini 复制代码
const handleClick = useCallback(() => {
  console.log(a);
}, [a]);

等价于:

ini 复制代码
const handleClick = useMemo(() => {
  return () => console.log(a);
}, [a]);

函数引用不变,避免子组件不必要的重新渲染


二、它们解决的核心问题只有一个

防止"引用类型"频繁变化,导致子组件不必要地 re-render

问题示例(没有 useCallback)

ini 复制代码
const Parent = () => {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    console.log('click');
  };

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

每次 Parent 渲染:

  • handleClick 都是新函数
  • Childprops.onClick 变化
  • Child 重新渲染(即使用了 React.memo

正确优化(使用 useCallback)

ini 复制代码
const handleClick = useCallback(() => {
  console.log('click');
}, []);

✅ 函数引用稳定

Child 只有在真正需要时才渲染


三、为什么"滥用"反而更慢?

1️⃣ useMemo / useCallback 本身有成本

它们不是"免费"的,内部做了这些事情:

  • 记录上一次的依赖数组
  • 每次 render 进行浅比较
  • 维护缓存结构

一句话:记忆化本身也是开销


2️⃣ 过早优化 = 负优化

scss 复制代码
const obj = useMemo(() => ({ a: 1 }), []);

这个对象创建成本极低,但:

  • React 每次 render 都要比较依赖数组
  • 还要维护缓存

收益 < 成本 → 性能反而下降


3️⃣ 破坏代码可读性

滥用后的代码:

scss 复制代码
const handleSubmit = useCallback(() => {
  doSomething();
}, []);

const data = useMemo(() => transform(props.data), [props.data]);

❌ 阅读成本变高

❌ 心智负担变重

❌ 后续维护者一脸懵


4️⃣ 依赖数组写错,Bug 更难查

scss 复制代码
const handleClick = useCallback(() => {
  setCount(count + 1);
}, []); // ❌ 闭包陷阱
  • 忘记加依赖
  • 拿到的是旧状态
  • 调试极其痛苦

四、React 本身已经很快了

React 的 Fiber 架构 + 高效的 diff 算法,已经能处理绝大多数场景。

大多数组件 re-render 的成本,比你想象的要低得多

除非你遇到以下情况,否则不要急着优化:

  • 列表长度 > 1000
  • 复杂可视化 / 表格
  • 频繁输入 / 拖拽
  • 明显卡顿(用 Profiler 验证)

五、什么时候"必须"用?

✅ 场景一:配合 React.memo 使用

ini 复制代码
const Child = React.memo(({ onClick }) => {
  return <button onClick={onClick}>Click</button>;
});

const Parent = () => {
  const handleClick = useCallback(() => {
    // ...
  }, []);

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

这是 useCallback 最经典、最正确的用法


✅ 场景二:昂贵的计算

ini 复制代码
const sortedList = useMemo(() => {
  return hugeList.sort(expensiveSort);
}, [hugeList]);

✅ 计算成本 >> 记忆化成本


✅ 场景三:作为其他 Hook 的依赖

scss 复制代码
const fetchData = useCallback(async () => {
  // ...
}, [token]);

useEffect(() => {
  fetchData();
}, [fetchData]);

✅ 避免 effect 无限执行


六、什么时候"不要"用?

❌ 普通函数

css 复制代码
const add = (a, b) => a + b;

✅ 没必要


❌ 简单对象

css 复制代码
const style = useMemo(() => ({ color: 'red' }), []);

✅ 直接写更清晰


❌ 只在当前组件使用的函数

ini 复制代码
const handleChange = useCallback(() => {
  setValue(e.target.value);
}, []);

✅ 没有传给子组件,没收益


❌ 为了"看起来专业"

dart 复制代码
// 不要这样
const isEven = useMemo(() => num % 2 === 0, [num]);

✅ 直接算更快


七、性能优化的正确顺序(非常重要)

第一步:让代码正确

第二步:让代码清晰

第三步:用 Profiler 找到瓶颈

第四步:针对性优化

不要在没有性能问题的时候"预防性优化"


八、一个真实的负优化案例

❌ 滥用前

javascript 复制代码
function List({ items }) {
  return items.map(item => <Item key={item.id} item={item} />);
}

❌ 滥用后

javascript 复制代码
function List({ items }) {
  const renderedItems = useMemo(() => {
    return items.map(item => <Item key={item.id} item={item} />);
  }, [items]);

  return renderedItems;
}

问题:

  • useMemo 每次都要 diff items
  • JSX 本身创建成本极低
  • 反而更慢 + 更难读

直接 return 才是正确做法


九、一句话总结

useMemouseCallback逃生舱 ,不是默认配置

它们不是用来让代码"更快"的,而是用来避免不必要的 re-render​ 的。

记住这条铁律:

如果你不能用 React DevTools Profiler 证明它有性能问题,就不要优化。


十、最佳实践口诀

能不用就不用

✅ **传给子组件才考虑 useCallback**​

✅ **计算很重才用 useMemo**​

先测量,再优化

可读性永远优先于微优化

相关推荐
神奇小汤圆1 小时前
Codex 子 Agent 配置指南:让 Sol 当军师,Luna 当搬砖工
后端
ClouGence2 小时前
2026 年 4 款数据库管理工具推荐:免费、开源、付费怎么选?
数据库·后端·开源
大勇前进2 小时前
React 中 State 为什么不能直接修改?很多新手一直没懂
后端
大黄评测2 小时前
useState 与 useReducer 该怎么选?别只会无脑用 useState
后端
乐橙开放平台2 小时前
物业 SaaS 笔记:子账号 Policy 按通道隔离,At_ 管控面 / St_ 数据面治理 accessToken
后端·物联网·音视频
站大爷IP2 小时前
Python的is和==把我坑惨了,原来对象比较的水这么深
后端
fatcoder2 小时前
玩转Nginx 04 — 反向代理:给 nginx 接上后端
前端·后端·nginx
雨落倾城夏未凉2 小时前
halcon核心-颜色识别/颜色控件转换(十)
后端
SomeB1oody2 小时前
【RustyML入门】5.2. 分类指标
开发语言·后端·机器学习·rust·教程