useCallback 是 React 中的一个 Hook,用于缓存函数(callback)的引用 ,避免在组件每次重新渲染时都创建一个新的函数实例。
🎯 核心目的:防止不必要的子组件重渲染或重复注册副作用
在 React 中,函数也是值。每次组件重新渲染时,如果直接写 () => {...} 或 function() {...},都会生成一个全新的函数对象。即使函数内容完全一样,JavaScript 也会认为它们是不同的(因为引用不同)。
这在以下场景会带来问题:
- 传递给
React.memo包裹的子组件 → 函数引用变化会导致子组件即使 props 未实质改变也重新渲染。 - 作为依赖项传给
useEffect、useMemo等 Hook → 函数变化会触发不必要的副作用或重新计算。
useCallback 就是用来解决这个问题的。
🔧 语法
js
const memoizedCallback = useCallback(
() => {
// 函数体
},
[dependencies] // 依赖数组
);
- 只有当依赖数组中的值发生变化时,才会返回一个新的函数;
- 否则,返回之前缓存的函数引用。
💡
useCallback(fn, deps)等价于useMemo(() => fn, deps)
🌰 举例说明
场景:父组件向子组件传递一个回调函数
js
import React, { useState, useCallback } from 'react';
// 子组件用 React.memo 包裹,只有 props 变化才重渲染
const Child = React.memo(({ onIncrement }) => {
console.log('Child 渲染了');
return <button onClick={onIncrement}>点我+1</button>;
});
function Parent() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
// ❌ 没用 useCallback:每次 Parent 渲染都会创建新函数
// const handleIncrement = () => setCount(c => c + 1);
// ✅ 使用 useCallback 缓存函数
const handleIncrement = useCallback(() => {
setCount(c => c + 1);
}, []); // 无依赖,只创建一次
return (
<div>
<p>Count: {count}</p>
<p>
Name: <input value={name} onChange={e => setName(e.target.value)} />
</p>
{/* 传递回调给子组件 */}
<Child onIncrement={handleIncrement} />
</div>
);
}
行为对比:
| 操作 | 不用 useCallback |
用了 useCallback |
|---|---|---|
修改 name(输入框) |
Parent 重渲染 → handleIncrement 是新函数 → Child 重渲染(尽管 onIncrement 逻辑没变) |
handleIncrement 引用不变 → Child 不重渲染 ✅ |
| 点击按钮 | 正常工作 | 正常工作 |
因为
Child用React.memo包裹了,它会浅比较 props。如果onIncrement引用变了,就认为 props 变了,于是重渲染。
⚠️ 注意事项
-
不要滥用
如果子组件没有用
React.memo,或者函数不是传给子组件/副作用,那用useCallback反而增加开销。 -
正确声明依赖
如果回调里用了状态或 props,必须加到依赖数组中,否则会捕获旧值(stale closure):
jsconst [count, setCount] = useState(0); const [step, setStep] = useState(1); // ❌ 错误:没把 step 加入依赖,永远用初始 step=1 const increment = useCallback(() => { setCount(c => c + step); // step 可能是过期的! }, []); // ← 缺少 step // ✅ 正确 const increment = useCallback(() => { setCount(c => c + step); }, [step]); -
它缓存的是"函数引用",不是"执行结果"
useMemo→ 缓存值useCallback→ 缓存函数本身
✅ 什么时候该用?
| 场景 | 建议使用 useCallback? |
|---|---|
把函数传给 React.memo 的子组件 |
✅ 是 |
函数作为 useEffect / useMemo 的依赖 |
✅ 是 |
| 函数只在当前组件内部调用,不传出去 | ❌ 否 |
| 组件很小,性能不敏感 | ❌ 通常不需要 |
总结一句话:
useCallback用于稳定函数引用,避免因函数"看似变化"而导致的不必要渲染或副作用,尤其在配合React.memo时非常有用。
如果你熟悉 Vue,可以类比:
- Vue 的方法(methods)天然不会在每次渲染时重建(因为绑定在组件实例上),所以一般不需要类似优化;
- 而 React 的函数组件每次执行都会重新定义内部函数,所以需要
useCallback来"冻结"函数引用。