useMemo 是一个 React Hook,用于缓存(记忆化)计算结果,避免在每次渲染时都重复执行昂贵的计算逻辑。它接受一个工厂函数和一个依赖项数组,只有当依赖项变化时,才会重新执行工厂函数并返回新的值;否则,直接返回上次缓存的结果。
基本语法
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
- 第一个参数:返回需要缓存的计算结果的函数。注意它必须是一个纯函数,不应该有副作用。
- 第二个参数:依赖项数组。当数组中的任何一个值发生变化时,才会重新执行工厂函数。
为什么需要 useMemo
React 的函数组件在状态或 Props 变化时会重新执行整个函数体。如果组件内部有复杂的数据处理(例如对长列表进行过滤、排序、聚合,或递归计算),这些计算会随每次渲染重复执行,即使结果可能完全一样,造成不必要的性能浪费。
useMemo 的本质是空间换时间:用内存空间存储上一次的结果,当依赖未变时直接复用,跳过耗时的计算过程。
使用场景与示例
示例1:避免昂贵的列表过滤
function ProductList({ products, category }) {
// 只有当 products 或 category 变化时才重新过滤
const filteredProducts = useMemo(() => {
return products.filter(p => p.category === category);
}, [products, category]);
return (
<ul>
{filteredProducts.map(p => (
<li key={p.id}>{p.name}</li>
))}
</ul>
);
}
如果没有 useMemo,每次父组件渲染造成 ProductList 重渲染时,即使 products 和 category 不变,过滤逻辑也会重新执行。对于数百条数据可能没问题,但对于数千条或更复杂的过滤规则,就会产生可感知的性能影响。
示例2:依赖派生状态
当一个组件使用了 useState 和 useReducer,并需要基于某些数据计算派生值时,useMemo 是理想的选择。
function Dashboard({ rawData }) {
const statistics = useMemo(() => {
// 假设这是一组耗时的聚合计算
return {
total: rawData.reduce((sum, item) => sum + item.amount, 0),
average: rawData.reduce((sum, item) => sum + item.amount, 0) / rawData.length,
max: Math.max(...rawData.map(item => item.amount)),
};
}, [rawData]);
return (
<div>
<p>总计:{statistics.total}</p>
<p>平均:{statistics.average}</p>
<p>最高:{statistics.max}</p>
</div>
);
}
示例3:优化传递给子组件的引用类型 Props
当把对象或数组作为 Props 传递给使用 React.memo 包裹的子组件时,如果该引用在每次渲染时都重新创建,会导致子组件即使数据没变也会重渲染。useMemo 可以稳定这些引用。
function Parent({ items }) {
// 避免每次渲染都创建新数组
const sortedItems = useMemo(() => {
return [...items].sort((a, b) => a.date - b.date);
}, [items]);
return <Child items={sortedItems} />;
}
const Child = React.memo(({ items }) => {
// Child 只有在 sortedItems 引用变化时才重新渲染
return <List data={items} />;
});
使用原则与注意事项
- 不要过早优化
useMemo 本身也有开销(存储和依赖比较)。对于简单的计算(如字符串拼接、简单的数学运算),使用 useMemo 可能得不偿失。只有当计算确实昂贵,且组件频繁渲染时才使用。
- 依赖项必须完整且正确
useMemo 的依赖数组应包含工厂函数内部使用到的所有响应式变量(state、props、其他 context 值等)。遗漏依赖会导致缓存失效和潜在的难以调试的 bug。推荐启用 eslint-plugin-react-hooks 的 exhaustive-deps 规则,它会自动检测缺失的依赖项。
- 不要用于副作用
useMemo 只能包含纯计算,不能用于数据获取、手动操作 DOM 等副作用。这些应使用 useEffect 或 useLayoutEffect。
- 不要依赖它来保证跨渲染的引用稳定性
虽然大部分情况下 useMemo 会返回相同的引用,但 React 可能在未来版本中在某些情况下丢弃缓存以释放内存。你的代码不应该强依赖这种缓存行为,而应将其视为性能优化手段。
- 与
useCallback的区别
useMemo 缓存的是计算结果(任何值),而 useCallback 是 useMemo 的特化版本,专门用于缓存函数引用。
useCallback(fn, deps) 等价于 useMemo(() => fn, deps)
总结
useMemo 是 React 性能优化工具箱中的重要一环。在遇到渲染性能瓶颈时,通过缓存昂贵的计算结果和稳定引用类型 Props,可以有效减少不必要的重渲染和重复计算。但务必谨慎使用,优先测量性能问题,再精准施加优化。