React useCallback
React 中缓存函数引用的 Hook 及其与 useMemo、普通函数声明的边界。
[!info] related notes
- 所属 MOC: React MOC
- 上位主题: react-hooks, react
- 易混淆概念: react-use-memo, React 组件渲染与重渲染
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 管函数身份,不是默认性能开关。