返回专题首页

React 专题

useMemo、useCallback 与性能判断:什么时候优化才有意义

React 里最容易被用成“护身符”的两个 API,大概就是 `useMemo` 和 `useCallback`。很多人一看到组件可能重渲,就本能地到处包一层 memo 化,好像这样项目就会更专业、更快。现实往往相反。没有判断基础的优化,通常只会增加阅读成本,未必带来真实收益。

React 专题第 07 篇 / 26 篇4 分钟

React 里最容易被用成“护身符”的两个 API,大概就是 useMemouseCallback。很多人一看到组件可能重渲,就本能地到处包一层 memo 化,好像这样项目就会更专业、更快。现实往往相反。没有判断基础的优化,通常只会增加阅读成本,未必带来真实收益。

这一篇真正要讲的,不是这两个 Hook 的语法,而是 React 性能判断的基本思路:什么时候问题真的和重复计算或引用稳定性有关,什么时候根因其实在状态边界、组件结构或错误的渲染设计上。

为什么性能优化第一步不是上优化 API?

因为性能问题来源很多:

  • 状态放得过高;
  • 组件拆分不合理;
  • 列表项过重;
  • 副作用过多;
  • 请求和渲染时机设计不好;
  • 资源体积或第三方依赖过重。

如果一上来就先套 useMemo / useCallback,很容易只是在表面做文章,而没有触及真正瓶颈。

useMemo 真正适合什么?

useMemo 更适合那类:

  • 计算本身确实有成本;
  • 输入不变时结果理应复用;
  • 结果又会被后续渲染或依赖链反复消费。

它的本质是在说:这段计算值得被缓存,而不是每次渲染都重做。

但如果计算本身很轻、依赖变化频繁、或者只是为了“让代码看起来更像高级 React 写法”,那通常并不值得。

useCallback 真正适合什么?

useCallback 更适合那些“函数引用稳定性本身有意义”的场景,例如:

  • 作为 props 传给经过 memo 化的子组件;
  • 作为某些 Hook 的依赖,需要避免无意义变化;
  • 某些订阅或注册逻辑依赖稳定回调。

它不是用来“所有函数都包一层”的。因为大多数普通组件内部函数,即使每次渲染重新创建,也未必构成真正问题。

为什么很多 React 项目越优化越难读?

因为问题不在有没有 memo 化,而在过度提前优化。典型现象包括:

  • 到处都是 useMemo,却说不清缓存了什么成本;
  • 到处都是 useCallback,但并没有任何依赖引用稳定性的真实需求;
  • 依赖数组越来越复杂;
  • 阅读代码时先要花很多力气理解“为什么这里被 memo 化”。

这类项目表面上像在做性能治理,实际上是在制造新的维护成本。

真正值得先看的性能问题通常是什么?

更常见的根因通常包括:

  • 本地状态被放得过高,带来整片区域重渲;
  • 列表项组件过重;
  • 某些派生数据本该提前整理;
  • Effect 触发了额外更新;
  • 组件边界不清,导致无关区域被一起带动。

这些问题不先解决,再怎么 memo 化,收益都有限。

性能判断放回项目里,应该怎么做?

更稳的顺序通常是:

1. 先确认慢在哪:计算、渲染、列表、资源还是请求;2. 再看状态边界和组件拆分是否合理;3. 真有重复重算或引用稳定性问题时,再考虑 useMemo / useCallback;4. 优化后看复杂度是否真的值得;5. 不要为了消除“可能的重渲”而过度设计。

这个顺序特别重要,因为 React 性能问题很多时候不是缺 Hook,而是缺判断。

最常见的几个误区

1. 一看到函数传 props 就条件反射 useCallback

如果子组件根本没依赖引用稳定性,这通常没有意义。

2. 一看到数组或对象计算就上 useMemo

很多计算成本其实非常低。

3. 不先看状态边界,就直接优化渲染

往往会绕开真正问题。

4. 过度优化导致依赖项和代码可读性急剧下降

长期看维护成本可能大于收益。

总结

useMemouseCallback 真正要解决的,不是“让代码显得会优化”,而是当计算成本或引用稳定性确实成为问题时,提供更细粒度的控制。React 性能治理的第一步永远是判断问题来源,而不是堆优化 API。只要你先看结构、状态和更新边界,再决定是否 memo 化,优化就会更稳,也更容易向团队解释。