useMemo
React useMemo 深度解析
一、从函数组件工作原理说起
1.1 渲染即调用
函数组件的本质就是一个 JavaScript 函数。每次 State 或 Props 发生变化时,React 就会调用这个函数:
- 执行函数体,生成新的虚拟 DOM(JSX)
- 与上一次的虚拟 DOM 进行 Diff 对比
- 将变化应用到真实 DOM
1.2 每次渲染都是独立的
每一次函数调用(渲染),其内部的所有变量、函数、表达式都会重新声明和计算。
这意味着——如果一个计算非常耗时(比如对十万条数据做过滤排序),即使触发重新渲染的只是一个无关的 state 变化,这个耗时计算也会被毫无意义地重复执行。
二、useMemo 是什么
useMemo 是一个 React Hook,用于缓存计算结果:
const cachedValue = useMemo(calculateValue, dependencies)
calculateValue:一个"纯函数",返回你希望缓存的值dependencies:依赖项数组,只有依赖发生变化时才重新执行calculateValue- 返回值:缓存的计算结果
useMemo不改变组件的行为,只改变计算的 时机——从"每次都算"变成"依赖变了才算"。
三、核心用途一:避免昂贵的重复计算
没有 useMemo
function MyComponent({ list, filter }) {
// 每次渲染都重新计算,即使 list 和 filter 没变
const filteredList = expensiveCalculation(list, filter);
return <div>{filteredList.map(item => ...)}</div>;
}
如果父组件另一个状态(比如 count)变了,MyComponent 重新渲染,expensiveCalculation 会被白白执行一次。
使用 useMemo
function MyComponent({ list, filter }) {
const filteredList = useMemo(
() => expensiveCalculation(list, filter),
[list, filter] // 只有这两个变了才重算
);
return <div>{filteredList.map(item => ...)}</div>;
}
现在,即使父组件 count 变了导致本组件重新渲染,只要 list 和 filter 引用没变,useMemo 直接返回上一次缓存的结果,跳过整个计算过程。
适用场景
| 场景 | 是否推荐 useMemo |
|---|---|
| 万级以上数据过滤/排序 | ✅ 强烈推荐 |
| 复杂 JSON 数据转换 | ✅ 强烈推荐 |
| 正则匹配/字符串解析 | ✅ 推荐 |
| 简单加减乘除 | ❌ 不推荐(useMemo 本身有开销) |
四、核心用途二:保持引用稳定,配合 React.memo
4.1 问题的根源
JavaScript 的引用类型规则:每次渲染时,{}、[]、() => {} 这样的字面量都会创建一个全新的对象,即使内容一模一样,内存地址也不同。
{} === {} // false,每次都是新对象
4.2 没有 useMemo 的连锁反应
function Parent() {
const [count, setCount] = useState(0);
// 每次 Parent 渲染都创建一个全新的 config 对象
const config = { theme: 'dark', size: 'large' };
return (
<div>
<button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
<Child config={config} />
</div>
);
}
const Child = React.memo(({ config }) => {
console.log('Child rendered!'); // 每次点按钮都会打印!
return <div>Theme: {config.theme}</div>;
});
流程:
点击按钮 → count 变 → Parent 重新渲染
→ 创建新 config(引用变了)
→ React.memo 浅比较 config → 引用不同
→ Child 重新渲染(浪费)
4.3 用 useMemo 修复
function Parent() {
const [count, setCount] = useState(0);
// config 引用被缓存,永远不变
const config = useMemo(() => ({ theme: 'dark', size: 'large' }), []);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
<Child config={config} />
</div>
);
}
现在 config 只创建一次,引用永远不变,Child 不会再被无端重渲染。
适用场景
| 场景 | 是否推荐 useMemo |
|---|---|
传给 React.memo 子组件的对象/数组/函数 |
✅ 强烈推荐 |
作为 useEffect 依赖的引用类型值 |
✅ 推荐(避免死循环) |
| 组件内临时用的普通对象 | ❌ 不推荐(没传给子组件就没必要) |
五、本质:以空间换时间
| 比喻 | 代价 | |
|---|---|---|
| 不用 useMemo | 每次现买现做 | 花时间(重复计算) |
| 使用 useMemo | 做完存抽屉,下次直接拿 | 花空间(缓存占内存) |
useMemo 的核心思想是:用一点点内存,换取计算时间上的大幅节省。
六、什么时候不该用 useMemo
useMemo 不是银弹,滥用反而可能拖慢性能:
// ❌ 画蛇添足:简单的计算,useMemo 的开销比计算本身还大
const total = useMemo(() => a + b, [a, b]);
// ❌ 画蛇添足:JSX 中直接用基础类型,不需要缓存
return <div>{count}</div>;
useMemo 的代价:每次渲染时,React 都要比较依赖项数组(浅比较),还要维护缓存池。如果计算本身不昂贵,这个开销 > 直接计算的开销。
判断标准:先有可感知的性能问题,再用 useMemo。不要为了用而用。
七、总结
| 维度 | 不用 useMemo | 用 useMemo |
|---|---|---|
| 昂贵计算 | 每次渲染重复算,浪费 CPU | 依赖不变则跳过,性能提升 |
| 引用稳定性 | 每次渲染新建对象,React.memo 防不住 |
引用缓存不变,子组件跳过渲染 |
| 内存 | 不占额外内存 | 多占一点缓存内存 |
| 使用原则 | 适合简单计算、非 props 传递的场景 | 精准针对已存在的性能瓶颈 |
一句话
先有性能问题,再用 useMemo。它的意义不是"让代码变快",而是"让相同输入不必重复计算"。

浙公网安备 33010602011771号