介绍
useMemo 是 React 中的一个 Hook,用于优化性能,通过缓存计算结果来避免不必要的重复计算。它适用于需要复杂计算的场景,或者需要保持引用稳定性的场景。
使用场景
- 复杂计算的优化
- 避免子组件不必要的重渲染(结合
React.memo 使用)
- 保持对象/数组引用的稳定性
代码示例
import React, { useMemo, useState } from 'react'
const ExpensiveCalculation = () => {
const [count, setCount] = useState(0)
const [multiplicand, setMultiplicand] = useState(1)
// 未优化的昂贵计算(每次渲染都会执行)
// const result = count * 1000 * Math.random()
// 使用useMemo优化后的计算(仅当count变化时重新计算)
const result = useMemo(() => {
console.log('执行昂贵计算...')
// 模拟复杂计算:时间复杂度 O(n^2)
let sum = 0
for(let i = 0
for(let j = 0
sum += i * j * count
}
}
return sum
}, [count])
return (
<div>
<h2>计算结果: {result}</h2>
<button onClick={() => setCount(c => c + 1)}>
改变依赖值 (+1)
</button>
<input
value={multiplicand}
onChange={(e) => setMultiplicand(Number(e.target.value))}
placeholder="输入不影响计算的数值"
/>
<p>无关状态值: {multiplicand}</p>
</div>
)
}
示例说明:
- 当点击"改变依赖值"按钮时,count变化触发重新计算
- 修改输入框数值时(改变multiplicand),不会触发昂贵计算
- 控制台可见"执行昂贵计算..."只在count变化时输出
进阶使用:优化组件渲染
import React, { useMemo } from 'react';
const UserList = ({ users, filterText }) => {
const filteredUsers = useMemo(() => {
console.log('执行用户过滤...');
return users.filter(user =>
user.name.toLowerCase().includes(filterText.toLowerCase())
);
}, [users, filterText]);
return (
<ul>
{filteredUsers.map(user => (
<li key={user.id}>{user.name} - {user.email}</li>
))}
</ul>
);
}
const UserDashboard = () => {
const [users, setUsers] = useState([]);
const [filterText, setFilterText] = useState('');
const [refreshCount, setRefreshCount] = useState(0);
useEffect(() => {
fetchUsers().then(data => setUsers(data));
}, [refreshCount]);
return (
<div>
<input
value={filterText}
onChange={(e) => setFilterText(e.target.value)}
placeholder="搜索用户..."
/>
<button onClick={() => setRefreshCount(c => c + 1)}>
刷新列表(共刷新 {refreshCount} 次)
</button>
{/* 优化的列表组件 */}
<UserList users={users} filterText={filterText} />
</div>
);
}
优化效果:
- 输入过滤文本时:仅当users或filterText变化时重新过滤
- 点击刷新按钮时:users数组更新触发重新过滤
- 父组件其他状态变化不会导致过滤计算重复执行
与 useCallback 的区别
| 特性 | useMemo | useCallback |
|---|
| 返回值 | 缓存的计算结果 | 缓存的函数引用 |
| 典型使用场景 | 避免昂贵计算重复执行 | 防止子组件不必要的重新渲染 |
| 等价写法 | useMemo(() => fn, deps) | useCallback(fn, deps) |
| 内存占用 | 存储计算结果 | 存储函数对象 |
转换示例:
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
const memoizedCallback = useMemo(() => {
return () => {
doSomething(a, b);
};
}, [a, b]);
最佳实践原则
- 按需使用原则
const total = items.length;
const total = useMemo(() => calculateSum(items), [items]);
- 依赖项精确原则
const user = useMemo(() => ({
name: firstName + ' ' + lastName,
age: currentAge
}), [firstName, lastName, currentAge]);
const user = useMemo(() => ({
name: firstName + ' ' + lastName,
}), [firstName]);
- 配合React.memo使用
const MemoizedComponent = React.memo(({ data }) => {
});
const Parent = () => {
const processedData = useMemo(() => process(rawData), [rawData]);
return <MemoizedComponent data={processedData} />
}
性能监控示例
// 创建一个带有性能测量的useMemo
function useProfiledMemo(factory, deps) {
const start = performance.now()
const value = useMemo(factory, deps)
const end = performance.now()
console.log(`计算耗时: ${(end - start).toFixed(2)}ms`)
return value
}
// 使用示例
const result = useProfiledMemo(() => {
// 复杂计算...
}, [deps])
常见误区解析
- 滥用缓存
const value = useMemo(() => 1 + 1, []);
- 依赖项缺失
const [count, setCount] = useState(0);
const double = useMemo(() => {
return count * 2;
}, []);
- 副作用滥用
const data = useMemo(() => {
fetchData();
return processData();
}, []);
适用场景总结
| 场景 | 示例 | 收益程度 |
|---|
| 复杂计算/数据转换 | 数组过滤、排序、数学计算 | ⭐️⭐️⭐️⭐️⭐️ |
| 大列表渲染 | 虚拟列表的可见项计算 | ⭐️⭐️⭐️⭐️ |
| 避免子组件不必要渲染 | 配合React.memo使用 | ⭐️⭐️⭐️⭐️ |
| 稳定引用(对象/数组) | 防止useEffect重复触发 | ⭐️⭐️⭐️ |
| 依赖第三方库计算 | 使用lodash进行复杂数据操作 | ⭐️⭐️⭐️⭐️ |
// 稳定对象引用示例
const config = useMemo(() => ({
color: theme === 'dark' ? '#fff' : '#000',
fontSize: 16,
responsive: true
}), [theme])