一、核心概念
1. useMemo 到底是啥?
大白话:useMemo 是 React 的 “计算结果缓存工具”—— 给它一个 “费时间的计算逻辑” 和 “依赖项列表”,它会把计算结果存起来(缓存);只有依赖项变了,才重新计算;依赖项没变,直接返回缓存的结果,避免重复做无用功。
生活比喻:就像你算完 “全家月开销” 后,把总数写在纸上(缓存);只要没新增消费(依赖项没变),下次要用到时直接看纸就行,不用再把所有账单加一遍。
二、用法格式(3 步搞定)
1. 基础格式
import { useMemo } from 'react';
// 缓存计算结果
const 缓存的结果 = useMemo(() => {
// 第一步:写需要缓存的计算逻辑(比如大数组处理、复杂运算)
return 复杂计算的结果;
}, [依赖项1, 依赖项2]); // 第二步:列依赖项(只有这些变了,才重新计算)
2. 关键说明
- 第一个参数:无参回调函数(里面写计算逻辑),useMemo 会自动执行这个函数,缓存它的返回值;
- 第二个参数:依赖项数组(必填,哪怕是空数组),控制是否重新计算;
- 返回值:缓存的 “计算结果”(不是函数,是直接能用的值)。
三、什么时候用 useMemo?
1. 3 个核心场景
只有满足以下条件,用 useMemo 才有用,否则就是 “画蛇添足”:
- 计算逻辑 “费性能”:比如处理大数组(筛选 / 排序 / 求和)、循环次数多、复杂数学运算(不是
a + b这种简单计算); - 组件会 “频繁重新渲染”:比如有计数器、输入框等频繁变化的状态,导致组件反复渲染;
- 计算的 “依赖项不常变”:计算逻辑依赖的变量(比如数组、对象、数字)不会每次渲染都变。
反例(不用用 useMemo):
// 错误:简单计算(a + b)用 useMemo,反而增加缓存开销
const sum = useMemo(() => a + b, [a, b]);
四、核心用法案例(贴近实际开发)
1. 案例:表格数据筛选 + 排序(缓存处理结果)
场景:表格有 1 万条数据,用户可以输入关键词筛选、选择排序方式,组件还有一个无关计数器 —— 只有筛选关键词 / 排序方式变了,才重新处理数据;计数器变了,直接用缓存的结果。
import { useState, useMemo } from 'react';
export default function UseMemoTest() {
// 1. 模拟1万条表格数据(组件外定义,避免重复创建)
const allData = Array(10000).fill().map((_, i) => ({ id: i, name: `用户${i}`, age: i % 50 + 10 }));
// 2. 状态(筛选关键词、排序方式、无关计数器)
const [searchKey, setSearchKey] = useState(''); // 筛选依赖项
const [sortType, setSortType] = useState('asc'); // 排序依赖项
const [count, setCount] = useState(0); // 无关状态
// 3. 用 useMemo 缓存“筛选+排序”结果(费性能的计算)
const processedData = useMemo(() => {
console.log('重新处理表格数据!'); // 观察触发时机
// 步骤1:筛选(含关键词的用户)
const filtered = allData.filter(item =>
item.name.includes(searchKey)
);
// 步骤2:排序(升序/降序)
const sorted = filtered.sort((a, b) => {
return sortType === 'asc' ? a.age - b.age : b.age - a.age;
});
return sorted;
}, [searchKey]); // 只依赖“筛选关键词”和“排序方式”
return (
<div>
{/* 无关计数器(测试用) */}
<button onClick={() => setCount(count + 1)}>计数器+1(不影响表格)</button>
<p>计数器:{count}</p>
{/* 筛选+排序控制 */}
<input
placeholder="输入用户名筛选"
value={searchKey}
onChange={(e) => setSearchKey(e.target.value)}
/>
<button onClick={() => setSortType(sortType === 'asc' ? 'desc' : 'asc')}>
切换排序(升/降)
</button>
{/* 表格渲染(用缓存的处理结果) */}
<table>
<thead><tr><th>ID</th><th>姓名</th><th>年龄</th></tr></thead>
<tbody>
{processedData.map(item => (
<tr key={item.id}>
<td>{item.id}</td><td>{item.name}</td><td>{item.age}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
2. 效果
- 点 “计数器 + 1”:组件重新渲染,但
searchKey和sortType没变,useMemo直接用缓存,控制台不打印 “重新处理表格数据”; - 输入筛选关键词 / 切换排序:依赖项变了,重新处理数据,控制台打印,表格更新。
五、关键注意项(避坑重点!)
1. 依赖项数组:不能漏、不能多、不能错
- ❶ 不能漏写依赖:计算逻辑中用到的所有外部变量,都必须放进依赖数组,否则会拿到 “过时的缓存结果”;
const processedData = useMemo(() => {
return allData.filter(...).sort((a,b) => sortType === 'asc' ? a.age - b.age : b.age - a.age);
}, [searchKey]); // 漏了 sortType,切换排序时不会重新计算
- ❷ 不能多写无关依赖:把不相关的变量放进依赖数组,会导致不必要的重新计算;
// 错误:count 是无关变量,放进依赖数组
const processedData = useMemo(() => {
return allData.filter(...).sort(...);
}, [searchKey, sortType, count]); // 点计数器时也会重新处理数据
- ❸ 空依赖数组:如果计算逻辑不依赖任何变量,用
[],表示 “只在组件首次渲染时计算一次,之后永远用缓存”;
const total = useMemo(() => {
return allData.reduce((sum, item) => sum + item.age, 0);
// 不依赖任何状态
}, []); // 只计算一次
2. 引用类型依赖:必须保证 “引用不变”
数组、对象、函数是 “引用类型”—— 哪怕内容没变,只要重新创建(比如组件内定义的数组),引用就变了,useMemo 会误以为 “依赖变了”,重新计算。
错误示例(引用类型导致无效缓存):
function Table() {
// 错误:组件每次渲染都会新建 allData 数组(引用变了)
const allData = Array(10000).fill().map((_, i) => ({ id: i, name: `用户${i}` }));
const processedData = useMemo(() => {
console.log('重新处理数据!');
return allData.filter(...);
}, [allData]); // 每次渲染 allData 引用都变,导致频繁重新计算
}
正确处理方式
- 方案 1:把引用类型放到组件外部(永久不变);
- 方案 2:用
useMemo或useRef缓存引用类型;
3. 不要滥用:简单计算不用 useMemo
useMemo 本身也有 “缓存开销”(要存储结果、对比依赖项),如果计算逻辑很简单(比如 a + b、str.split(',')),用 useMemo 反而会降低性能。
// 错误:简单计算用 useMemo(没必要)
const sum = useMemo(() => a + b, [a, b]);
const arr = useMemo(() => str.split(','), [str]);
// 正确:直接计算
const sum = a + b;
const arr = str.split(',');
4. 只缓存 “计算结果”,不缓存 “函数 / 副作用”
- useMemo 是用来缓存 “纯计算结果” 的(没有副作用:不发请求、不修改 DOM、不更新状态);
- 如果要缓存 “函数”,用
useCallback(和 useMemo 类似,但专门缓存函数); - 如果要做 “副作用”(发请求、弹提示),用
useEffect,不要用 useMemo。
// 错误:用 useMemo 做副作用(发请求)
useMemo(() => {
fetch('/api/data').then(res => res.json()); // 副作用,不应该用 useMemo
}, [searchKey]);
// 正确:副作用用 useEffect
useEffect(() => {
fetch('/api/data').then(res => res.json());
}, [searchKey]);
5. React 版本注意:useMemo 不是 “铁打的缓存”
-
useMemo 的缓存是 “可选的”——React 在内存紧张时,可能会丢弃缓存,下次渲染重新计算(这种情况极少);
-
不要依赖 useMemo 缓存 “必须唯一” 的值(比如唯一 ID、请求结果),如果需要强制缓存,用
useRef。
六、常见错误对比(避坑指南)
| 错误用法 | 错误原因 |
|---|---|
useMemo((nums) => nums.reduce(...), [userNumbers]) | 给回调函数加了参数,useMemo 不会传值,参数是 undefined |
useMemo(() => { fetch('/api'); }, [searchKey]) | 用 useMemo 做副作用(发请求) |
useMemo(() => a + b, [a, b]) | 简单计算滥用 useMemo |
useMemo(() => { ... }, []) 但计算依赖外部变化的变量 | 漏写依赖项,导致拿到过时结果 |
七、总结(4 句话记牢)
- 核心功能:缓存 “费性能的计算结果”,减少重复计算;
- 触发逻辑:只有 “依赖项变化” 才重新计算,无关状态变化不触发;
- 关键注意:依赖项要写全、引用类型要稳、不滥用、不做副作用;
- 等价关系:React useMemo ≈ Vue computed(都是缓存计算结果)。
记住:useMemo 的目标是 “优化性能”,如果用了之后没提升(甚至变慢),不如不用~