useMemo 与 useCallback
本教程共 50 篇 · 第 33 篇 · 更新于 2026-07-29 · 约 7 分钟阅读
33. useMemo 与 useCallback
本节目标:理解 useMemo 和 useCallback 的作用,掌握引用相等性,知道什么时候该用、什么时候不该用。
先理解引用相等
JavaScript 里,对象和函数每次创建都是新引用:
// 每次渲染都创建新对象
function Component() {
const options = { limit: 10 }; // 新引用
// ...
}
// 每次渲染都创建新函数
function Component() {
const handleClick = () => {}; // 新引用
// ...
}
{ limit: 10 } === { limit: 10 } 是 false,因为引用不同。
引用相等 vs 值相等
Object.is({}, {})返回false。React 用Object.is比较依赖,引用不同就认为变了。
useMemo:缓存计算结果
useMemo 在依赖不变时返回缓存值,避免重复计算:
const cachedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
function TodoList({ todos, filter }) {
// 只有 todos 或 filter 变化时才重新计算
const visibleTodos = useMemo(() => {
return todos.filter(t => t.status === filter);
}, [todos, filter]);
return <ul>{visibleTodos.map(...)}</ul>;
}
useMemo 不是银弹
简单计算(字符串拼接、小数组遍历)不需要 useMemo。只有真正耗时的计算才值得缓存。
useCallback:缓存函数引用
useCallback 在依赖不变时返回同一个函数引用:
const cachedFn = useCallback(fn, dependencies);
function ProductPage({ productId }) {
// 依赖不变,handleSubmit 引用不变
const handleSubmit = useCallback((orderDetails) => {
post(`/product/${productId}/buy`, { orderDetails });
}, [productId]);
return <ShippingForm onSubmit={handleSubmit} />;
}
什么时候需要 useCallback
单独用 useCallback 没有意义。它的价值在于配合 memo 使用:
import { memo } from 'react';
const ShippingForm = memo(function ShippingForm({ onSubmit }) {
// 只有 props 变化才重新渲染
return (/* ... */);
});
没有 useCallback,每次父组件渲染都创建新的 handleSubmit,memo 形同虚设:
// 没有 useCallback:每次都是新函数,memo 失效
function ProductPage() {
const handleSubmit = (orderDetails) => { /* ... */ };
return <ShippingForm onSubmit={handleSubmit} />; // 每次都重渲染
}
// 有 useCallback:引用稳定,memo 生效
function ProductPage() {
const handleSubmit = useCallback((orderDetails) => { /* ... */ }, [deps]);
return <ShippingForm onSubmit={handleSubmit} />; // props 不变不重渲染
}
不要滥用 useCallback
如果子组件没用 memo,或者函数不传给子组件,useCallback 只是白白增加代码复杂度。
useMemo vs useCallback
| useMemo | useCallback | |
|---|---|---|
| 缓存什么 | 计算结果(值) | 函数本身 |
| 返回值 | 函数的返回值 | 函数本身 |
| 等价写法 | useMemo(() => fn, deps) | useCallback(fn, deps) |
// 这两行效果相同
const fn1 = useMemo(() => () => console.log('hi'), []);
const fn2 = useCallback(() => console.log('hi'), []);
什么时候不该用
不需要 useMemo:
- 计算很简单(字符串拼接、数字运算)
- 结果不是传给 memo 子组件的 props
不需要 useCallback:
- 函数不传给子组件
- 子组件没有用 memo
- 函数只在当前组件内部用
确实需要 useCallback:
- 函数传给用了 memo 的子组件
- 函数作为 effect 的依赖
// 不需要:计算太简单
const fullName = useMemo(() => firstName + ' ' + lastName, [firstName, lastName]);
// 直接算就行
const fullName = firstName + ' ' + lastName;
// 不需要:子组件没 memo
const handleClick = useCallback(() => {}, []);
return <button onClick={handleClick}>点击</button>;
一个实际有用的场景
function Chart({ data, filters }) {
// 数据转换很耗时,缓存结果
const processedData = useMemo(() => {
return data
.filter(item => item.category === filters.category)
.map(item => ({ ...item, value: item.value * 100 }))
.sort((a, b) => b.value - a.value);
}, [data, filters]);
// 传给子组件的回调,保持引用稳定
const handleHover = useCallback((item) => {
console.log('hover', item.name);
}, []);
return <ExpensiveChart data={processedData} onHover={handleHover} />;
}
const ExpensiveChart = memo(function ExpensiveChart({ data, onHover }) {
// 渲染很耗时,props 不变就不重渲染
});
React Compiler 的影响
React 19 的 React Compiler 能自动 memoization,减少手动 useMemo/useCallback 的场景。但在编译器普及之前,理解这些 Hook 仍然很重要。
先学原理,再谈自动化
即使有 React Compiler,理解引用相等和 memoization 的原理,对排查性能问题仍然至关重要。
本节要点
- 引用相等:每次渲染创建新对象/函数
- useMemo 缓存计算结果,useCallback 缓存函数引用
- 配合 memo 使用才有意义
- 简单场景不需要,不要过度优化