useMemo
要理解useMemo首先要明白React函数式组件是如何工作的
React 函数组件工作原理
渲染即调用:函数组件的本质就是一个 JavaScript 函数,每次状态(State)或属性(Props)发生变化时,React 就会调用这个函数来生成新的虚拟 DOM(JSX),并与之前的进行对比(Diff),最后将变化应用到真实 DOM 上。每次渲染都是独立的:每一次函数调用(渲染),其内部的所有变量、函数、表达式都会重新声明和计算,这包括可能非常耗时的计算(例如,对一个包含成千上万条数据的列表进行过滤和排序)
性能浪费
我们知道函数渲染组件的工作原理后,有些场景会导致性能浪费。
举个例子
假设有一个计算量很大的函数 expensiveCalculation:
function MyComponent({ list, filter }) {
// 每次渲染,无论 list 或 filter 是否改变,都会重新计算!
const filteredList = expensiveCalculation(list, filter);
return <div>{filteredList.map(item => <div key={item.id}>{item.name}</div>)}</div>;
}
如果list和filter没有变化,但父组件的某个其他状态(比如一个计数器count)发生了变化,导致MyComponent重新渲染,这个耗时的操作也会被毫无必要地再次执行,造成计算资源的浪费和潜在的卡顿。
为什么要使用useMemo?
useMemo的出现就是为了解决上述例子中的性能问题,核心目的就是优化。
主要目的
主要目的是避免昂贵的重复计算,这是useMemo最根本、最直接的用途,说直白点就是保持引用相等,避免子组件不必要的渲染。
它通过记忆化(Memoization)技术,只有在依赖发生变化时才会重新执行昂贵的计算函数。
function MyComponent({ list, filter }) {
// 使用 useMemo:只有当 [list, filter] 改变时,才重新计算
const filteredList = useMemo(() => {
return expensiveCalculation(list, filter);
}, [list, filter]); // 依赖项
return <div>{filteredList.map(...)}</div>;
}
现在即使父组件的count状态变了,导致MyComponent重新渲染,只要list和filter没变,useMemo就会直接返回上一次缓存的计算结果,完全跳过expensiveCalculation的执行,从而极大的提升性能。
保持引用相等,避免子组件不必要的渲染
这是一个非常重要且常见的应用场景,涉及到javascript的引用类型和React的渲染性能优化。
- javascript的问题:每次组件渲染时,像 {}、[]、()=> {} 这样的字面量都会创建一个全新的对象/数组/函数,即使内容一模一样,它们在内存中的地址(引用)也是不同的。
- React的机制:当父组件渲染时,默认其所有子组件都会重新渲染。为了优化,我们常用
React.memo()包裹子组件,让它对外部传入的Props进行浅比较。如果所有的Props的引用都没变,就跳过子组件的渲染。
没有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>
{/* 即使用了 React.memo,Child 也会因为 config 引用不同而重新渲染 */}
<Child config={config} />
</div>
);
}
const Child = React.memo(({ config }) => {
console.log('Child rendered!'); // 每次点击按钮都会打印
return <div>Theme: {config.theme}</div>;
});
点击按钮修改 count,Parent 重新渲染,创建了新的 config 对象。虽然 config 的内容没变,但它的引用变了,导致 React.memo 的浅比较失败,Child 组件被迫重新渲染。
使用useMemo的解决方案
function Parent() {
const [count, setCount] = useState(0);
// 使用 useMemo 缓存 config 对象,依赖项为空数组,意味着它只创建一次
const config = useMemo(() => ({ theme: 'dark', size: 'large' }), []);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
{/* 现在 config 的引用始终不变,Child 不会被不必要的渲染 */}
<Child config={config} />
</div>
);
}
现在,无论 Parent 因为 count 重新渲染多少次,config 的引用始终保持不变,Child 组件因此避免了不必要的重渲染。
总结:为什么要使用 useMemo?
| 原因 | 描述 | 解决的问题 |
|---|---|---|
| 避免昂贵计算 | 缓存复杂计算的结果,仅在依赖变更时重新计算。 | 性能浪费:防止无关的渲染导致耗时操作重复执行。 |
| 保持引用稳定 | 缓存对象、数组等引用类型,保证其依赖不变时引用不变。 | 不必要的子组件渲染:与 React.memo 配合,避免因 props 引用变化导致的子组件重渲染。 |
核心思想:useMemo 是一种以空间换时间的优化策略(用一点内存来存储缓存结果,换取计算时间上的节省)。它不应该被滥用,而应针对性地应用于已存在或可预见的性能瓶颈上。

浙公网安备 33010602011771号