一、核心区别
- useMemo:缓存 计算结果(值)
- useCallback:缓存 函数本身
它们的工作原理完全一样 :依赖不变 → 直接用缓存;依赖变了 → 重新生成。
二、useMemo 详细讲解
1.作用
缓存一个值(对象、函数、计算结果),避免组件重新渲染时重复计算。
2.语法
const 缓存值 = useMemo(计算函数, 依赖数组);
3.工作流程
组件第一次渲染:执行计算结果 =》存结果
组件后续渲染:如果依赖数组里的值没变 =》直接用缓存,不执行计算函数。如果依赖数组里任意一个值变了 =》 重新执行计算函数并更新缓存。
4.什么时候必须要用useMemo?
第一种情况:耗时计算(最常见用法)
// 大数据过滤、排序、复杂计算
const expensiveResult = useMemo(() => {
return hugeList.filter(...).map(...).sort(...)
}, [hugeList]);
不用 useMemo → 组件一渲染就重新算 → 会导致页面卡顿
第二种情况:缓存引用类型(对象/数组)
// 每次渲染都会生成新对象
const style = { color: 'red' };
// 用 useMemo 缓存,引用永远不变
const style = useMemo(() => ({ color: 'red' }), []);
为什么要缓存引用?
因为对象 / 数组是引用类型,每次渲染都会变新地址,会导致子组件不必要重渲染。
怎么理解会导致子组件不必要重渲染?
父组件每更新一次,函数重新执行一遍。只要你在组件里直接写对象/数组:
function Parent(){
// 每次渲染 = 新建一个全新对象,新内存地址
const obj = { id:1, name:"张三" }
return <Child data={obj}/>
}
每一次重渲染 → 生成全新引用地址
子组件用了React.memo 做浅层对比优化:
- 子组件接收data props
- 父渲染 → obj 换新地址
- memo 对比发现props 地址变了
- 直接判定 props 变了 → 强制重渲染子组件
- 明明内容没变,纯浪费性能
什么时候不要用useMemo?
- 固定加减、字符串拼接
- 固定不变的值。缓存本身也有开销,过度用反而会变慢。
三、useCallback 详细讲解
1.作用
缓存函数本身,让函数在依赖数组不变时,永远时同一个函数引用。
2.语法
const 缓存函数 = useCallback(函数体, 依赖数组);
3.工作流程
组件首次渲染:执行useCallback,创建函数 + 固定内存地址,返回缓存好的函数。
组件再次重渲染,先对比依赖数组:如果依赖没变 =》直接返回旧函数、旧地址,不新建函数。如果依赖变了 =》重新创建函数,更新引用地址。
再传给子组件,搭配React.memo,函数引用不变 =》props 不变 =》子组件不重复渲染。
4.为什么要用useCallback?
看这个例子你立刻懂:
function Parent() {
const handleClick = () => { console.log('click') };
return <Child onClick={handleClick} />
}
问题:Parent 一渲染 → handleClick 就变成新函数 → Child 收到新 props → Child 被迫重渲染。
用 useCallback 修复:
const handleClick = useCallback(() => {
console.log('click')
}, []); // 依赖空数组 → 永远不变
5. useCallback 必须配合 memo 使用!
单独用 useCallback 没用!必须配合子组件的memo 才能生效:
// 子组件用 memo 包裹:只有 props 变了才渲染
const Child = memo(({ onClick }) => { ... });
现在 Child 不会因为父组件渲染而重复渲染。
为什么useCallback 必须配合memo 使用?
React 默认规则是,父组件一更新,所有子组件无条件跟着重新渲染。不管你函数地址变没变,照样渲染。
而useCallback 只能把函数地址稳住不变。父组件管他怎么渲染,只要依赖不变,函数地址永远不变。但他自己不能阻止任何渲染。
memo 是子组件是否要渲染的判断开关。React.memo(子组件),作用:子组件自带 props 浅层对比。props 全没变 =》跳过渲染。props 变了 =》正常渲染
完整生效链路
- 父组件用useCallback =》函数引用不变
- 子组件包memo =》检测到 props 没变化
- 最终:子组件不重渲染
四、一个惊人的等价关系
观察下面代码:
// useMemo → 缓存值
const value = useMemo(() => {
return 100; // 返回值
}, []);
// useCallback → 缓存函数
const fn = useCallback(() => {
return 100; // 函数体
}, []);
useCallback(fn, deps) 完全等于useMemo(() => fn, deps)
也就是说:useCallback 是useMemo 的语法糖,专门用来缓存函数。
五、useMemo 和React.memo
React.memo 是组件级别缓存,防止组件重复渲染。作用是给函数组件做浅层props 比对,props 没变就跳过重渲染,props 变了就正常渲染。
useMemo 是值级别缓存,缓存计算结果/引用。作用是根据依赖数组判断是否需要重新执行计算函数,避免重新执行不必要的复杂计算。
需要注意的是,如果使用useMemo 缓存函数,也需要配合React.memo 使用,优化才会生效。
六、小结
-
useMemo 缓存值,useCallback 缓存函数
-
它们都靠依赖数组决定是否更新
-
目的都是避免不必要的重渲染 / 重复计算
-
useCallback 必须配合memo 子组件才能真正优化