React 中 useCallback 和 useMemo 的区别与使用场景
在 React 中,useCallback 和 useMemo 的核心目的都是缓存(memoization),减少不必要的重新创建和重新计算,从而避免不必要的子组件重渲染或昂贵计算。但它们缓存的东西不同。
一句话总结:
useCallback 缓存函数,useMemo 缓存计算结果。
一、定义层面的区别
1️⃣ useCallback
1 | const fn = useCallback(() => { |
等价于:
1 | const fn = useMemo(() => { |
作用:
- 缓存的是:函数引用本身
- 只要依赖不变,函数地址不变
- 主要用于:防止子组件因为 props 中函数引用变化而重复渲染
2️⃣ useMemo
1 | const value = useMemo(() => { |
作用:
- 缓存的是:计算结果(值)
- 只有依赖变化时,才会重新执行计算
- 主要用于:避免昂贵计算在每次 render 时重复执行
二、解决的问题本质不同
| Hook | 解决什么问题 | 缓存什么 |
|---|---|---|
| useCallback | 函数引用不稳定导致子组件重复渲染 | 函数 |
| useMemo | 计算结果重复计算,性能浪费 | 值 |
三、典型使用场景
场景 1:传函数给 memo 子组件
React.memo 用于缓存函数组件的渲染结果,当 props 浅比较不变时,跳过重新渲染,常用于配合 useCallback / useMemo 避免无意义刷新。
1 | // React.memo = 给函数组件加一层“缓存壳子”,props 不变就不重新 render。 |
❌ 不用 useCallback:每次 Parent render,函数都是新对象 → Child 重渲染
✅ 用 useCallback:函数引用稳定 → Child 不会无意义刷新
场景 2:昂贵计算
❌ 不使用 useMemo:
1 | function DataList({ type }) { |
✅ 使用 useMemo 优化:
1 | function DataList({ type }) { |
效果对比:
- ❌ 不用 useMemo:每次 render 都执行昂贵计算,浪费性能
- ✅ 用 useMemo:只在依赖变化时重新计算,提升性能
四、常见误区
❌ 误区 1:滥用 useCallback / useMemo
它们不是“性能银弹”。
- 本身也有成本(依赖比较 + 闭包)
- 小函数 / 小计算用它们可能更慢
❌ 误区 2:useCallback 会阻止函数内部执行?
不是。
useCallback 只保证函数引用不变,不是“函数不执行”。
❌ 误区 3:useMemo = 性能优化必用
不对。
只有在:
- 计算很重
- 或用于 memo 子组件的 props 对比
才值得用。
五、工程实践判断标准
可以用这三条判断:
这个值 / 函数是否会传给
React.memo子组件?- 如果是,用
useCallback(函数)或useMemo(对象/数组)保持引用稳定 - 避免子组件因 props 引用变化而无意义重渲染
- 如果是,用
这个计算是否明显昂贵?
- 例如:filter 大数组(>1000 项)、复杂排序、图形计算、正则匹配
- 如果是,用
useMemo缓存结果
这个对象/函数/数组是否会作为
useEffect/useCallback/useMemo的依赖?- 如果是,用
useMemo或useCallback稳定引用 - 避免因引用变化导致 effect 重复执行或依赖链连锁更新
- 示例:
1
2
3
4
5const options = useMemo(() => ({ id: userId }), [userId]);
useEffect(() => {
fetchData(options); // options 引用稳定,避免重复请求
}, [options]);
- 如果是,用
满足任意一条 → 值得用 memo / callback
六、快速决策与面试总结
一句话决策
| 你要干嘛 | 用什么 |
|---|---|
| 稳定函数引用 | useCallback |
| 缓存计算结果 | useMemo |
| 两者都不是 | 都别用 |
面试核心回答
useCallback 是 useMemo 的语法糖,用来缓存函数引用;useMemo 用来缓存计算结果。两者都是为了解决引用不稳定或重复计算导致的性能问题,但应当只在必要时使用。
常见追问及答案
Q1: useCallback 和 useMemo 有性能开销吗?
A: 有。每次 render 时都需要:① 比较依赖数组;② 维护闭包。所以不要滥用,只在真正有性能问题时使用。
Q2: 什么时候不应该用它们?
A: ① 简单的原始值计算(如
a + b);② 不会传给子组件的普通函数;③ 组件 render 本身就很快的情况。
Q3: 如何判断是否需要优化?
A: 使用 React DevTools Profiler 测量组件渲染时间,只在确认有性能瓶颈时才优化。过早优化是万恶之源。
Q4: useCallback 的依赖数组为空和非空有什么区别?
A:
- 空数组
[]:函数只在组件首次渲染时创建,之后永远不变- 非空
[a, b]:当 a 或 b 变化时,函数会重新创建- 不传依赖:每次 render 都创建新函数(失去了 useCallback 的意义)