最近在 review 代码时,发现不少同学对 useMemo 的使用存在误解:
- 有的地方该用不用,导致性能浪费;
- 有的地方滥用,反而增加开销;
- 更有人把它和
useRef、useCallback混为一谈……
今天,我就用一个简单例子 + 三大对比 + 一个灵魂拷问,带你彻底搞懂 useMemo 的正确打开方式!
一、先看一个经典场景:为什么需要 useMemo?
假设我们有一个计数器,还有一个“切换主题”按钮:
问题来了:当我点击“切换主题”时,squared 会重新计算吗?
✅ 答案是:会!
因为每次状态更新都会触发组件重新执行,count * count 就会被重复计算。
虽然乘法很快,但如果这是个复杂操作(如排序、格式化、深度克隆),就会造成不必要的性能损耗。
二、useMemo:只在依赖变化时才计算
解决方案就是 —— useMemo!
现在:
- 点击 +1 → 重新计算 ✅
- 点击 切换主题 → 不计算 ❌(控制台无日志)
✨
useMemo的作用:缓存计算结果,依赖不变就不重算。
三、三大缓存 Hook 对比:别再混用了!
很多同学分不清 useMemo、useCallback、useRef,其实它们目标完全不同:
| Hook | 缓存什么? | 典型用途 | 返回值 |
|---|---|---|---|
useRef | 一个可变容器(.current) | 存 DOM 引用、定时器 ID、跨渲染变量 | { current: T } |
useCallback | 函数 | 防止函数重建导致子组件重渲染 | Function |
useMemo | 任意值(对象/数组/计算结果) | 避免重复执行昂贵计算 | T |
💡 一句话区分:
- 想缓存一个值(不是函数)→
useMemo - 想缓存一个函数 →
useCallback - 想存一个不触发渲染的变量 →
useRef
⚠️ 常见误区:
“我用useRef存计算结果不就行了?”
→ 技术上可行,但你得手动判断依赖是否变化,极易出错且不可维护!
四、灵魂拷问:React 的 useMemo vs Vue 的 computed?
很多从 Vue 转 React 的同学会问:
“
useMemo是不是就等于 Vue 的computed?”
答案是:相似,但底层机制完全不同!
| 特性 | Vue computed | React useMemo |
|---|---|---|
| 响应式原理 | 自动依赖追踪(Proxy 劫持) | 手动声明依赖(依赖数组) |
| 更新粒度 | 细粒度(只更新用到的数据) | 组件级(整个函数重执行) |
| 是否自动缓存 | ✅ 是 | ✅ 是(但需手动写) |
| 心智模型 | “数据变了,视图自动跟上” | “状态变了,我重新描述 UI” |
🌰 举个例子:
在 Vue 中,你写computed: { fullName() { return this.firstName + this.lastName } },框架自动知道它依赖firstName和lastName。
而在 React 中,你必须显式写出[firstName, lastName],否则会用旧值!
✅ 所以:
- Vue 的 computed 更“智能”
- React 的 useMemo 更“显式可控”
五、使用 useMemo 的 3 个最佳实践
-
不要过早优化
简单计算(如a + b)不需要useMemo,反而增加内存开销。 -
依赖数组必须写全
漏掉依赖会导致“stale closure”(闭包陷阱),用 ESLint 插件exhaustive-deps自动检查。 -
主要用于以下场景:
- 昂贵的计算(排序、过滤、格式化)
- 创建新对象/数组(避免子组件因引用变化重渲染)
- 传递 props 给
React.memo包裹的子组件
六、总结
useMemo不是万能药,但它是性能优化的关键工具。- 它和
useCallback、useRef各司其职,不要混用。 - 与 Vue 的
computed相比,React 更强调显式依赖声明,这是函数式思维的核心。
🔑 记住:
“useMemo 缓存的是值,useCallback 缓存的是函数,useRef 存的是盒子。”