介绍
useMemo
是 React 中的一个 Hook,用于优化性能,通过缓存计算结果来避免不必要的重复计算。它适用于需要复杂计算的场景,或者需要保持引用稳定性的场景。
使用场景
- 复杂计算的优化
- 避免子组件不必要的重渲染(结合
React.memo
使用)
- 保持对象/数组引用的稳定性
代码示例
ini
复制代码
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; i < 10000; i++) {
for(let j = 0; j < 10000; j++) {
sum += i * j * count;
}
}
return sum;
}, [count]); // ✅ 只在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变化时输出
进阶使用:优化组件渲染
javascript
复制代码
import React, { useMemo } from 'react';
// 用户列表组件
const UserList = ({ users, filterText }) => {
// 使用useMemo缓存过滤结果
const filteredUsers = useMemo(() => {
console.log('执行用户过滤...');
return users.filter(user =>
user.name.toLowerCase().includes(filterText.toLowerCase())
);
}, [users, filterText]); // 依赖users数组和过滤文本
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);
// 模拟API请求
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) |
内存占用 |
存储计算结果 |
存储函数对象 |
转换示例:
scss
复制代码
// 这两个写法完全等效
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
// useMemo实现相同功能
const memoizedCallback = useMemo(() => {
return () => {
doSomething(a, b);
};
}, [a, b]);
最佳实践原则
- 按需使用原则
scss
复制代码
// 不需要缓存的情况(简单计算)
const total = items.length; // ✅ 直接使用
// 需要缓存的情况(复杂计算)
const total = useMemo(() => calculateSum(items), [items]); // ✅
- 依赖项精确原则
scss
复制代码
const user = useMemo(() => ({
name: firstName + ' ' + lastName,
age: currentAge
}), [firstName, lastName, currentAge]); // ✅ 精确依赖
// 错误示例(遗漏依赖)
const user = useMemo(() => ({
name: firstName + ' ' + lastName,
}), [firstName]); // ❌ lastName变化时不会更新
- 配合React.memo使用
javascript
复制代码
const MemoizedComponent = React.memo(({ data }) => {
/* 渲染逻辑 */
});
const Parent = () => {
const processedData = useMemo(() => process(rawData), [rawData]);
return <MemoizedComponent data={processedData} />
}
性能监控示例
ini
复制代码
// 创建一个带有性能测量的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]);
常见误区解析
- 滥用缓存
scss
复制代码
// 错误:缓存简单计算反而更慢
const value = useMemo(() => 1 + 1, []); // ❌ 毫无意义
- 依赖项缺失
scss
复制代码
const [count, setCount] = useState(0);
const double = useMemo(() => {
return count * 2; // ❌ 缺少count依赖
}, []); // 当count变化时不会更新
- 副作用滥用
scss
复制代码
// 错误:在useMemo中执行副作用
const data = useMemo(() => {
fetchData(); // ❌ 副作用应放在useEffect中
return processData();
}, []);
适用场景总结
场景 |
示例 |
收益程度 |
复杂计算/数据转换 |
数组过滤、排序、数学计算 |
⭐️⭐️⭐️⭐️⭐️ |
大列表渲染 |
虚拟列表的可见项计算 |
⭐️⭐️⭐️⭐️ |
避免子组件不必要渲染 |
配合React.memo使用 |
⭐️⭐️⭐️⭐️ |
稳定引用(对象/数组) |
防止useEffect重复触发 |
⭐️⭐️⭐️ |
依赖第三方库计算 |
使用lodash进行复杂数据操作 |
⭐️⭐️⭐️⭐️ |
ini
复制代码
// 稳定对象引用示例
const config = useMemo(() => ({
color: theme === 'dark' ? '#fff' : '#000',
fontSize: 16,
responsive: true
}), [theme]); // 当theme变化时才生成新对象