1.核心定义
useCallback 是 React 提供的性能优化钩子 ,作用是:缓存函数引用,避免组件重渲染时重复创建相同逻辑的函数。
2. 为什么需要 useCallback?
React 中,组件重渲染时(如父组件状态变化),内部定义的函数会被重新创建(引用地址改变)。如果这个函数被传给子组件作为 props,会导致子组件「无意义重渲染」(即使子组件用了 memo 优化)。
示例(无 useCallback 的问题):
tsx
// 父组件
const Parent = () => {
const [count, setCount] = useState(0);
// 每次Parent重渲染,handleClick都会重新创建(引用变了)
const handleClick = () => {
console.log('点击');
};
return (
<div>
<button onClick={() => setCount(count + 1)}>计数{count}</button>
{/* 即使Child用了memo,handleClick引用变了,Child仍会重渲染 */}
<Child onClick={handleClick} />
</div>
);
};
// 子组件(用memo优化,只有props变了才重渲染)
const Child = memo(({ onClick }) => {
console.log('Child重渲染了');
return <button onClick={onClick}>子组件按钮</button>;
});
3. useCallback 基本用法
tsx
const memoizedFn = useCallback(
() => {
// 函数逻辑
doSomething(a, b);
},
[a, b] // 依赖数组:只有依赖项变化时,才重新创建函数
);
- 参数 1:需要缓存的函数;
- 参数 2 :依赖数组(和 useEffect 一致):
- 依赖项不变 → 复用之前缓存的函数引用;
- 依赖项变化 → 创建新的函数,更新缓存。
4. 修复上面的示例(用 useCallback)
tsx
const Parent = () => {
const [count, setCount] = useState(0);
// 缓存函数:依赖数组为空,只有组件首次渲染时创建一次
const handleClick = useCallback(() => {
console.log('点击');
}, []); // 无依赖 → 永久缓存
return (
<div>
<button onClick={() => setCount(count + 1)}>计数{count}</button>
{/* Child不会再无意义重渲染 */}
<Child onClick={handleClick} />
</div>
);
};
const Child = memo(({ onClick }) => {
console.log('Child重渲染了');
return <button onClick={onClick}>子组件按钮</button>;
});
5. 核心原理
useCallback 内部维护了一个「缓存池」,逻辑简化如下:
tsx
function useCallback(callback, deps) {
// 取出上一次缓存的函数和依赖
const [cachedFn, cachedDeps] = getFromCache();
// 对比依赖数组是否变化(浅比较)
if (depsChanged(cachedDeps, deps)) {
// 依赖变了 → 缓存新函数和新依赖
cacheSet(callback, deps);
return callback;
}
// 依赖没变 → 返回缓存的函数
return cachedFn;
}
关键点:React 对依赖数组做浅比较(===),如果依赖项是对象 / 数组,即使内容没变但引用变了,也会重新创建函数。
6. 关键注意事项
(1) 不是所有函数都需要用 useCallback
- 仅当函数作为 props 传给子组件 且子组件用了
memo时,才需要用; - 仅当函数作为 useEffect/useMemo 的依赖 时,才需要用;
- 组件内部自用的函数(不传给子组件、不做依赖),用 useCallback 反而增加性能开销(没必要)。
(2) 依赖数组必须写全
- 函数内部用到的所有变量 / 状态,都要加入依赖数组(否则会捕获旧值,导致逻辑错误);
- 依赖数组为空 → 函数永久缓存,内部只能拿到组件首次渲染的状态 / 变量。
示例(依赖数组错误的坑):
tsx
const [count, setCount] = useState(0);
// 错误:函数内部用了count,但没加入依赖
const badFn = useCallback(() => {
console.log(count); // 永远打印0,因为依赖为空,函数缓存了首次的count
}, []);
// 正确:加入所有依赖
const goodFn = useCallback(() => {
console.log(count); // 能拿到最新的count
}, [count]);
(3) 和 useMemo 的区别
useCallback:缓存函数引用 →useCallback(fn, deps)≈useMemo(() => fn, deps);useMemo:缓存函数执行结果(适用于计算密集型操作)。
7. 典型适用场景
- 函数作为 props 传给 memo 包装的子组件;
- 函数作为 useEffect 的依赖(避免 useEffect 频繁执行);
- 函数被多个地方复用,且创建成本高(如复杂的回调逻辑);
- 配合 React.memo/useMemo 做整体性能优化。