useCallback 详细解释(从原理到用法)(自学用)

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. 典型适用场景

  1. 函数作为 props 传给 memo 包装的子组件;
  2. 函数作为 useEffect 的依赖(避免 useEffect 频繁执行);
  3. 函数被多个地方复用,且创建成本高(如复杂的回调逻辑);
  4. 配合 React.memo/useMemo 做整体性能优化。
相关推荐
a1117761 小时前
粒子化系统(3D-Particles)THreeJS react
前端·html·jetson
码农君莫笑2 小时前
深入理解 CSS Grid 布局:从入门到实战
前端·css
Vu4612 小时前
nextjs的图片和文字优化
react.js
yingyima2 小时前
Azure Functions 定时触发器配置:Cron vs. TimerTrigger,谁主沉浮?
前端
TeamDev2 小时前
JxBrowser 9.1.1 版本发布啦!
java·前端·chromium·混合应用·jxbrowser·嵌入式浏览器·浏览器控件
爱勇宝2 小时前
如何评估 AI 大模型的商业价值?
前端·后端·程序员
蜡台3 小时前
UniApp WebView 组件宽高设置与动态适配全方案
前端·javascript·uniapp·webview·iframe
半个烧饼不加肉3 小时前
JS 底层探究-- 调用栈(Call Stack)
开发语言·前端·javascript
子午3 小时前
基于DeepSeek的智能校园教务管理系统~Web管理系统+Vue3+Python+DeepSeek智能问答
前端