useCallback
面试速答(30 秒版 TL;DR)
useCallback(fn, deps)的本质是:缓存函数引用,只有依赖变了才返回新函数。- 它解决的不是“函数执行慢”,而是“函数身份频繁变化”导致的额外渲染或副作用重跑。
- 最典型场景是:配合
React.memo、作为依赖传给别的 Hook、或传给深层子组件避免无意义更新。 - 面试里一定要补一句:不要滥用
useCallback,它本身也有依赖维护和心智成本。
1. 心智模型
组件每次渲染都会重新执行函数体:
function Parent() {
const handleClick = () => {
console.log('click')
}
}
即使函数内容一样,handleClick 也是新的函数引用。
如果这个函数被传给:
React.memo包裹的子组件useEffect依赖数组- 自定义 Hook
就可能触发额外更新。
useCallback 的核心就是:让“函数内容没必要变”时,函数引用也尽量别变。
2. 基本用法
const handleSubmit = useCallback(() => {
save(id)
}, [id])
可以粗略理解成:
- 首次渲染时缓存这个函数
- 后续渲染时,如果
id没变,就复用旧引用 - 如果
id变了,就创建新函数并替换缓存
3. 它通常和谁一起出现
3.1 React.memo
const Child = React.memo(function Child({onClick}: {onClick: () => void}) {
return <button onClick={onClick}>提交</button>
})
如果父组件每次都传入新的函数引用,React.memo 的浅比较就会失效。
3.2 作为其他 Hook 的依赖
const fetchData = useCallback(() => {
return request(keyword)
}, [keyword])
useEffect(() => {
fetchData()
}, [fetchData])
如果不稳定,useEffect 可能每次 render 都重跑。
4. React 18 里怎么理解它的价值
React 18 引入并发渲染能力后,组件重渲染更常见,但这不代表“所有函数都必须缓存”。
更稳的理解是:
- 只有当函数身份稳定真的能带来收益时才用
- 如果子组件本来就会因为别的 props 更新,单独缓存函数往往意义不大
- 如果只是为了“消除 ESLint 报警”而乱加,通常会把依赖关系搞得更复杂
5. 面试高频答法
Q1:useCallback 和 useMemo 的区别?
useCallback缓存的是函数本身useMemo缓存的是函数执行结果
一句话记忆:
一个保“函数引用”,一个保“计算结果”。
Q2:useCallback 能优化性能吗?
能,但前提是“函数引用稳定”这件事本身能减少额外工作。
如果没有:
memo- 依赖比较
- 订阅解绑
这类场景,收益通常不明显。
Q3:为什么说它不能滥用?
因为它不是免费:
- 要维护依赖数组
- 容易引入闭包过期问题
- 本身也会增加代码噪音和理解成本
常见追问
1)不用 useCallback 会有 bug 吗?
多数时候不会,只是可能多一些不必要更新。
2)它能避免闭包问题吗?
不能。它只是缓存函数引用,不会自动修复你闭包里拿到的旧值问题。
易错点/坑
- 把
useCallback理解成“缓存函数执行结果”。 - 见到事件处理函数就无脑包一层
useCallback。 - 依赖写不全,导致闭包拿到旧状态。
速记要点(可背诵)
useCallback:缓存函数引用,不缓存执行结果。- 典型场景:
React.memo、Hook 依赖、稳定回调引用。 - 不是默认优化项,只在“引用稳定有价值”时使用。