React 里最容易被用成“护身符”的两个 API,大概就是 useMemo 和 useCallback。很多人一看到组件可能重渲,就本能地到处包一层 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. 过度优化导致依赖项和代码可读性急剧下降
长期看维护成本可能大于收益。
总结
useMemo 和 useCallback 真正要解决的,不是“让代码显得会优化”,而是当计算成本或引用稳定性确实成为问题时,提供更细粒度的控制。React 性能治理的第一步永远是判断问题来源,而不是堆优化 API。只要你先看结构、状态和更新边界,再决定是否 memo 化,优化就会更稳,也更容易向团队解释。