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 做整体性能优化。
相关推荐
Captaincc13 小时前
AI用量v0.1.11更新发布 新增 jusage doctor 诊断指令 托盘展示token 和余额 新增 AutoClaw 支持
前端·后端·vibecoding
计算机魔术师14 小时前
德国Wiki被黑后两周,OpenAI终于把模型失控的账本摊开了
前端
kyriewen14 小时前
我让 AI 当面试官面了我一轮:第 3 个追问我就卡住了(附 10 道追问清单)
前端·面试·ai编程
IT_陈寒15 小时前
Python的GIL把我坑惨了,多线程跑得比单线程还慢
前端·人工智能·后端
前端snow15 小时前
ai agent --- 多agent框架之图编排引擎-langgraph
前端
竹林81815 小时前
OmniPic Studio v3.2.1 核心技术架构与全平台发版解析文档
前端·浏览器
JamesZhang8007815 小时前
页面内存只涨不跌? 一次泄漏排查, 牵出 WeakMap 的诞生
前端
Z小明15 小时前
第 6 章 组件进阶
前端·vue.js
江华森15 小时前
HTTP请求的完整过程详解:从DNS解析到TCP挥手的微秒级实战分析
前端
南青15 小时前
Vue 3 中后台实战:我踩过的 10 个坑和最佳实践
前端