React useCallback

React 中缓存函数引用的 Hook 及其与 useMemo、普通函数声明的边界。

#tech / dev / frame #resource / react #type / concept #status / growing

[!info] related notes

React useCallback

useCallback 用于缓存函数引用,避免在依赖没变时每次渲染都创建一个新的函数身份。

一句话定义

如果你关心的是“这个函数引用要不要保持稳定”,那是 useCallback 的问题。

两个最典型的使用场景

传给 memo 子组件

const handleClick = useCallback(() => {
  doSomething(id)
}, [id])

return <Child onClick={handleClick} />

如果 Child 依赖 props 引用稳定性,这里才可能有实际收益。

被其他 Hook 作为依赖

const fetchData = useCallback(() => {
  return fetch(`/api/users/${userId}`)
}, [userId])

useEffect(() => {
  fetchData()
}, [fetchData])

否则函数每次渲染都会变,effect 也会跟着反复重跑。

什么时候它有意义

  • 这个函数会作为 props 传给经过 memo 优化的子组件
  • 这个函数会被别的 Hook 作为依赖使用
  • 你明确知道函数身份变化会触发额外工作

什么时候没必要用

  • 只是普通组件里的简单事件处理函数
  • 没有性能瓶颈,却提前过度优化

useMemo 的区别

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

可以粗略把它理解成:

const fn = useCallback(callback, deps)

约等于:

const fn = useMemo(() => callback, deps)

常见误区

  • 认为所有事件处理函数都必须 useCallback
  • 为了避免重建函数而让代码可读性变差
  • 以为 useCallback 能阻止函数创建

实际上组件每次渲染时,代码依然会执行。它主要价值不是“少创建函数”,而是“在依赖没变时返回稳定引用”。

最短记忆方式

useCallback 管的是函数身份稳定,不是函数执行更快。

面试要点

来自 react-use-callback-overuse-interview-question 的面试视角整理。

一句话回答

如果函数引用变化本身没有引发额外问题,那到处使用 useCallback 往往只是增加代码复杂度,而不一定带来真实收益。

最稳的回答主线

  • useCallback 只在函数身份稳定很重要时才有意义
  • 常见场景是传给 memo 子组件,或作为其他 Hook 的依赖
  • 普通事件处理函数大多数情况下没必要机械包一层

一个更完整的面试表达

可以这样答:

useCallback 缓存的是函数引用,不是让函数执行更快。它只有在“函数身份变化会带来额外成本”时才有意义,比如这个函数要传给 React.memo 子组件,或者它本身是其他 Hook 的依赖。
如果只是普通按钮点击事件,大多数时候没必要为了“显得专业”就包一层 useCallback,那样通常只是增加代码复杂度。

典型有意义的场景

const handleSubmit = useCallback(() => {
  post(data)
}, [data])

return <ExpensiveChild onSubmit={handleSubmit} />

如果 ExpensiveChild 依赖 props 引用稳定性,这时 useCallback 才可能带来收益。

一个加分点

如果面试官继续追问,可以补一句:

useCallback 也不会阻止函数在渲染时被创建,它主要价值是让 React 在依赖不变时返回稳定引用。

为什么容易被滥用

  • 误以为所有函数都必须稳定
  • 误以为“缓存”天然比“不缓存”更快
  • 没有 profiling,就先上微优化

一个更工程化的结论

先保证状态边界、依赖数组、组件拆分是对的,再考虑 useCallback。很多性能问题根本不是函数引用导致的。

最短记忆方式

useCallback 管函数身份,不是默认性能开关。

创建于 2026/3/19 更新于 2026/5/27