
React 学习笔记:Memo Hook
发布于: 2026-06-09 22:47:20 更新于: 2026-06-09 22:47:20
useMemo 基础
什么是 useMemo?
useMemo 是 React 提供的性能优化 Hook,用于缓存计算结果。它的核心思想是:
如果依赖项没有变化,就直接返回上次计算的结果,避免重复计算。
这种"缓存返回值以避免重复计算"的做法,在计算机科学中叫做记忆化(Memoization),useMemo 这个名字就来源于此。
基本用法
import { useMemo } from "react";
function TodoList({ todos, tab }) {
// 只有 todos 或 tab 变化时,才会重新计算 visibleTodos
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...
}
参数说明
useMemo(calculateValue, dependencies) 接收两个参数:
| 参数 | 说明 |
|---|---|
| calculateValue | 计算函数,必须是纯函数,无参数,返回需要缓存的值 |
| dependencies | 依赖数组,包含计算函数中引用的所有响应式值(props、state、组件内变量) |
返回值
- 首次渲染:调用
calculateValue并返回结果,同时缓存该结果 - 后续渲染:检查依赖项是否变化
- 未变化:直接返回缓存值,跳过计算
- 已变化:重新调用
calculateValue,返回新结果并更新缓存
什么时候执行
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
// ↑
// 仅在 a 或 b 变化时才执行
核心使用场景
场景 1:缓存昂贵的计算
这是 useMemo 最主要的用途。当某个计算非常耗时,且输入参数不经常变化时,用 useMemo 避免每次渲染都重新计算。
不使用 useMemo(每次渲染都计算):
function ProductList({ products, filter }) {
// ❌ 每次渲染都重新过滤,即使 products 和 filter 没有变化
const filteredProducts = products.filter((p) => p.category === filter);
return (
<ul>
{filteredProducts.map((p) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
);
}
使用 useMemo(只在依赖变化时计算):
function ProductList({ products, filter }) {
// ✅ 只有 products 或 filter 变化时才重新过滤
const filteredProducts = useMemo(
() => products.filter((p) => p.category === filter),
[products, filter],
);
return (
<ul>
{filteredProducts.map((p) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
);
}
场景 2:避免子组件不必要的重新渲染
当你有一个用 React.memo 包裹的子组件,且该子组件的 props 是对象或数组时,useMemo 可以确保引用稳定:
const MemoizedChild = React.memo(ChildComponent);
function Parent() {
const [count, setCount] = useState(0);
// ❌ 每次 Parent 渲染都会创建新的 style 对象,导致 MemoizedChild 重新渲染
// const style = { color: 'red', fontSize: '16px' };
// ✅ 只在首次渲染时创建,后续渲染返回同一个引用
const style = useMemo(() => ({ color: "red", fontSize: "16px" }), []);
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<MemoizedChild style={style} />
</div>
);
}
场景 3:缓存复杂对象的引用
function ChatRoom({ roomId }) {
const [message, setMessage] = useState("");
// ✅ options 对象的引用保持稳定
const options = useMemo(
() => ({
serverUrl: "https://localhost:1234",
roomId: roomId,
}),
[roomId],
);
// 这里的 useEffect 只会在 roomId 变化时重新执行
useEffect(() => {
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [options]);
return <input value={message} onChange={(e) => setMessage(e.target.value)} />;
}
useMemo vs 不使用 useMemo
对比示例
function FilteredList({ items, query }) {
// 方式 1:直接计算(无缓存)
const filtered1 = items.filter((item) => item.name.includes(query));
// 方式 2:使用 useMemo(有缓存)
const filtered2 = useMemo(
() => items.filter((item) => item.name.includes(query)),
[items, query],
);
// filtered1:每次渲染都会执行 filter
// filtered2:只有 items 或 query 变化时才执行 filter
}
何时使用 useMemo?
| 场景 | 是否使用 useMemo |
|---|---|
| 计算量大(大数组过滤/排序/转换) | ✅ 使用 |
| 计算量小(简单运算) | ❌ 不需要 |
| 需要稳定引用(传给 memo 子组件) | ✅ 使用 |
| 每次都需要最新值 | ❌ 不需要 |
| 结果作为其他 Hook 的依赖项 | ✅ 使用 |
注意事项
1. 只在组件顶层调用
function MyComponent() {
// ✅ 正确:在组件顶层调用
const result = useMemo(() => heavyCompute(), [dep]);
// ❌ 错误:不能在条件语句或循环中调用
if (condition) {
const result = useMemo(() => heavyCompute(), [dep]); // 不要这样做!
}
}
2. 依赖数组必须完整
function MyComponent({ a, b, c }) {
// ❌ 错误:缺少依赖 c
const result = useMemo(() => a + b + c, [a, b]);
// ✅ 正确:包含所有使用的响应式值
const result = useMemo(() => a + b + c, [a, b, c]);
}
3. 不要滥用 useMemo
function SimpleComponent({ name }) {
// ❌ 不需要:这只是简单的字符串拼接
const greeting = useMemo(() => `Hello, ${name}!`, [name]);
// ✅ 直接写即可
const greeting = `Hello, ${name}!`;
}
记住:useMemo 本身也有开销(存储缓存、比较依赖)。对于简单计算,直接执行可能比缓存更快。
useMemo vs useCallback
这是最容易混淆的两个 Hook,它们的关系是:
// useCallback 缓存的是函数本身
const memoizedFn = useCallback(() => {
doSomething(a, b);
}, [a, b]);
// 等价于
const memoizedFn = useMemo(() => {
return () => {
doSomething(a, b);
};
}, [a, b]);
| Hook | 缓存什么 | 用途 |
|---|---|---|
useMemo |
计算结果(值) | 缓存昂贵计算的结果 |
useCallback |
函数引用 | 缓存函数定义,避免子组件重新渲染 |
实际应用示例
示例:搜索过滤 + 性能优化
import { useState, useMemo } from "react";
function SearchableList({ items }) {
const [query, setQuery] = useState("");
const [sortBy, setSortBy] = useState("name");
// 缓存过滤结果
const filteredItems = useMemo(() => {
console.log("执行过滤...");
return items.filter((item) =>
item.name.toLowerCase().includes(query.toLowerCase()),
);
}, [items, query]);
// 缓存排序结果(依赖 filteredItems)
const sortedItems = useMemo(() => {
console.log("执行排序...");
return [...filteredItems].sort((a, b) => {
if (sortBy === "name") return a.name.localeCompare(b.name);
if (sortBy === "price") return a.price - b.price;
return 0;
});
}, [filteredItems, sortBy]);
return (
<div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="搜索..."
/>
<select value={sortBy} onChange={(e) => setSortBy(e.target.value)}>
<option value="name">按名称排序</option>
<option value="price">按价格排序</option>
</select>
<ul>
{sortedItems.map((item) => (
<li key={item.id}>
{item.name} - ¥{item.price}
</li>
))}
</ul>
</div>
);
}
总结
useMemo 的核心价值:
- 缓存计算结果,避免重复计算
- 稳定引用,避免子组件不必要的重新渲染
- 优化性能,特别是在处理大数据集时
使用原则:
- 优先用于昂贵的计算(大数组操作、复杂转换)
- 用于稳定对象/数组引用(传给 memo 子组件)
- 不要滥用,简单计算直接执行即可
- 确保依赖数组完整,包含所有使用的响应式值
标签分类
# React# 前端