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 做整体性能优化。
相关推荐
成旭先生4 分钟前
AI 文本审核 API:一段中文文本判风险等级、命中标签与处置建议
java·前端·人工智能·api接口·内容风控·文本审核·ugc审核
liangshanbo121514 分钟前
前端面试题:AI 对话中超长消息导致内存溢出,怎么解决?
java·开发语言·前端
静默回滚16 分钟前
PDF能打开却不能复制,原来是权限密码
前端
律宏阔24 分钟前
Dart FFI 内存管理:用 using + Arena 替代嵌套 try-finally
前端·flutter
guslegend36 分钟前
脚手架入门:必要性、核心功能与执行原理
前端·架构·node.js·脚手架·前端工程化
律宏阔38 分钟前
Flutter 调用 Go:从 c-shared + ffigen 到 @Native + Native Assets 踩坑记录
前端·flutter
LEE40 分钟前
前端转型全栈 05:SQL 与迁移,AI 写的 SQL 怎么安全上线
前端·后端·ai编程
用户157415681653440 分钟前
macOS 打包体积异常的排查实录
前端
数据掘金40 分钟前
小程序埋点方案上线前要检查哪些点?我用一张检查清单过了二十多项
前端
Hooray43 分钟前
后台管理框架存活率大调查(2026版)
前端