跳到主要内容

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:useCallbackuseMemo 的区别?

  • useCallback 缓存的是函数本身
  • useMemo 缓存的是函数执行结果

一句话记忆:

一个保“函数引用”,一个保“计算结果”。

Q2:useCallback 能优化性能吗?

能,但前提是“函数引用稳定”这件事本身能减少额外工作。

如果没有:

  • memo
  • 依赖比较
  • 订阅解绑

这类场景,收益通常不明显。

Q3:为什么说它不能滥用?

因为它不是免费:

  • 要维护依赖数组
  • 容易引入闭包过期问题
  • 本身也会增加代码噪音和理解成本

常见追问

1)不用 useCallback 会有 bug 吗?

多数时候不会,只是可能多一些不必要更新。

2)它能避免闭包问题吗?

不能。它只是缓存函数引用,不会自动修复你闭包里拿到的旧值问题。

易错点/坑

  • useCallback 理解成“缓存函数执行结果”。
  • 见到事件处理函数就无脑包一层 useCallback
  • 依赖写不全,导致闭包拿到旧状态。

速记要点(可背诵)

  • useCallback:缓存函数引用,不缓存执行结果。
  • 典型场景:React.memo、Hook 依赖、稳定回调引用。
  • 不是默认优化项,只在“引用稳定有价值”时使用。