React 中 useCallback 的基本使用和原理解析

React 中 useCallback 的基本使用方法

useCallback 是 React 的一个核心 Hook,用于​缓存函数定义​,避免组件重新渲染时重复创建函数实例。以下是其基本使用方法:

1. 基本语法

javascript 复制代码
const memoizedCallback = useCallback(
  () => {
    // 函数逻辑 (例如更新状态、调用API等)
    doSomething(a, b);
  },
  [a, b] // 依赖项数组
);
  • 第一个参数:需要缓存的函数。
  • 第二个参数:依赖项数组(Dependency Array),当数组中的变量变化时,函数会重新创建。

2. 核心作用

  • 避免不必要的函数重建 :默认情况下,组件每次渲染都会创建新的函数实例,使用 useCallback 后可复用函数。
  • 优化子组件渲染 :当缓存的函数作为 props 传递给子组件(配合 React.memo)时,可避免子组件不必要的重渲染。

3. 使用示例

jsx 复制代码
import React, { useState, useCallback } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  // 缓存函数:依赖项为空数组,函数只创建一次
  const increment = useCallback(() => {
    setCount(prev => prev + 1); // 使用函数式更新避免闭包问题
  }, []);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+1</button>
    </div>
  );
}
  • 依赖项 [] 表示函数仅在组件初次渲染时创建。
  • 使用 setCount(prev => prev + 1) 替代 setCount(count + 1) 可避免闭包陷阱(函数捕获过时状态)。

4. 适用场景

useCallback,本质上是用于缓存函数。

如果函数,是以props的方式,传递给子组件,为了每次避免子组件的渲染,建议使用useCallback进行包裹。

但是每一次,使用useCallback,我们考虑的首要问题是,这样真的优化了组件的性能吗?其实大多数场景,如果不是类似列表渲染的场景,这样不一定会优化了性能。

也就是,函数作为props传递给性能敏感的子组件的场景,才是使用useCallback的时候。

useCallback 的原理解析

  • useCallback 的主要目的是在依赖项不变的情况下,返回同一个函数引用,避免函数重复创建,从而优化性能。
  • useCallback它会在首次渲染时(或依赖项变化时)创建一个新的函数,并将其缓存起来。在后续渲染中,如果依赖项没有变化,则返回缓存的函数;否则,就重新创建函数并更新缓存。
  • 简易的伪代码,可能如下所示
javascript 复制代码
let lastDeps; // 上一次的依赖项
let lastCallback; // 上一次缓存的函数

function useCallback(callback, deps) {
  if (lastDeps === undefined) {
    // 第一次调用
    lastDeps = deps;
    lastCallback = callback;
    return callback;
  }

  // 检查依赖项是否变化
  const hasChanged = deps.some((dep, index) => dep !== lastDeps[index]);
  if (hasChanged) {
    lastDeps = deps;
    lastCallback = callback;
  }
  return lastCallback;
}

每次掉用useCallback,返回的函数,取决于依赖项有没有发生变化。

React内部是咋样的呢?

1、Fiber 节点存储机制

React 在 Fiber 节点(组件实例对应的数据结构)中维护一个 memorizedState 链表,专门存储 Hooks 状态。

javascript 复制代码
function updateCallback(callback, deps) {
  const hook = updateWorkInProgressHook(); // 获取当前 Hook 节点
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;     // 读取缓存的上次状态
  
  // 依赖项对比:使用浅比较(shallow equal)
  if (prevState !== null && areHookInputsEqual(nextDeps, prevState[1])) {
    return prevState[0]; // 返回缓存的函数
  }
  
  //  依赖变化:缓存新函数
  hook.memoizedState = [callback, nextDeps];
  return callback;
}

2、依赖项对比算法

源码中的 areHookInputsEqual 对依赖数组进行浅比较(类似 Object.is):

javascript 复制代码
function areHookInputsEqual(nextDeps, prevDeps) {
  if (prevDeps === null) return false;
  for (let i = 0; i < prevDeps.length; i++) {
    if (!Object.is(nextDeps[i], prevDeps[i])) {
      return false;
    }
  }
  return true;
}

这种优化避免了深度比较的性能损耗

相关推荐
SamChan907 小时前
WebSocket实现PDF翻译进度实时推送:Go后端+React前端的完整方案
前端·websocket·pdf·c#·react·机器翻译
名字还没想好☜8 小时前
React 受控输入框光标跳到末尾:格式化输入时的 selection 丢失 bug 与修复
前端·javascript·react.js·bug·react·next.js
名字还没想好☜1 天前
Next.js App Router 约定式文件实战:loading、error、not-found 怎么兜住加载态与异常
开发语言·前端·javascript·react·next.js
名字还没想好☜2 天前
React createPortal 实战:模态框逃出 overflow:hidden、事件冒泡与焦点管理
前端·javascript·react.js·ecmascript·react·createportal
人间凡尔赛4 天前
2026 前端全栈开发新范式:React Compiler + Signals + RSC 实战指南
前端·性能优化·react·全栈·signals·rsc
linsk19984 天前
React18、19如何兼容 IE9、IE10
react·rollup·vite·兼容·ie
李燚5 天前
ReAct Agent 源码拆解:Eino 如何把 Graph 变成 Agent(第64篇-E50)
ai·agent·react·graph·multi-agent·aiagent·eino
zhoupenghui1686 天前
大模型核心技术ReAct和Agentic RAG讲解
人工智能·ai·大模型·react·rag·动态推理·agentic rag
人间凡尔赛7 天前
React Compiler + Server Components 实战:2026 前端全栈开发新范式
typescript·react·next.js·全栈开发·react compiler·server components
人间凡尔赛9 天前
2026 年 React Server Components 完全指南:Next.js 16 全栈开发最佳实践
前端·typescript·react·全栈·next.js