《深入浅出react》总结之 11. 2. 3 Hooks 初始化流程- useState

Hooks 初始化

知道了执行函数组件的函数,以及不同的 Hooks 对象之后,我们看一下在组件初始化的时候,

Hooks 的处理逻辑,Hooks 初始化流程使用的是 mountState、mountEffect 等初始化节点的 Hooks,将Hooks 和 fiber 建立起联系,那么如何建立起关系呢,每一个 Hooks 初始化都会执行 mountWorkInProgressHook,接下来看一下这个函数。

js 复制代码
function mountWorkInProgressHook() {
    const hook: Hook = {
         memoizedState: null,
         // useState 中保存着 state 信息 | useEffect 中保存着 effect 对象 | useMemo 中保存的是缓存的值和deps | useRef 中保存的是 ref 对象 baseState: null,
         baseQueue: null,
         queue: null,
         next: null,
    };
    if (workInProgressHook = = = null) { 
        // 例子中的第一个 hooks→ useState(0) 走的就是这样。
        currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
    } else {
         workInProgressHook = workInProgressHook.next = hook;
    }
    return workInProgressHook;
}

1. Hook对象初始化(mountWorkInProgressHook)

首先,每个Hook都是一个对象,其结构如下:

javascript 复制代码
const hook = {

    memoizedState: null, // 存储Hook的具体状态

    baseState: null, // 用于更新队列的基础状态(主要用于useState/useReducer)

    baseQueue: null, // 未处理的更新队列(主要用于useState/useReducer)

    queue: null, // 更新队列(保存待处理的更新和dispatch函数)

    next: null, // 指向下一个Hook,形成链表

};

在函数组件中,多个Hook通过next指针形成链表,而fiber节点的memoizedState指向这个链表的头节点。

2. 三种Hook的初始化细节

a) useState

初始化useState会调用mountState

javascript 复制代码
function mountState(initialState) {

    const hook = mountWorkInProgressHook(); // 创建一个新的Hook对象

    hook.memoizedState = hook.baseState =

    typeof initialState === 'function' ? initialState() : initialState;

    const queue = {

        pending: null, // 待处理的更新队列(循环链表)

        dispatch: null, // 更新函数

        lastRenderedReducer: basicStateReducer, // 用于计算状态的reducer

        lastRenderedState: hook.memoizedState, // 上一次渲染的状态

    };

    hook.queue = queue;

    const dispatch = (queue.dispatch = dispatchAction.bind(

        null,

        currentlyRenderingFiber,

        queue

    ));

    return [hook.memoizedState, dispatch];

}
  • 状态存储 :初始状态存储在hook.memoizedStatehook.baseState

  • 更新队列 :创建一个队列对象,用于存储后续的更新(如多次调用setState)。

  • dispatch函数 :返回的更新函数(setNumber)被绑定到当前fiber和队列上,确保更新能正确调度。

2. dispatchAction 更新机制

当我们调用 setNumber 时,实际上是在调用 dispatchAction 函数。它的核心逻辑如下:

js 复制代码
function dispatchAction(fiber, queue, action) {
// 1. 获取更新优先级(Lane模型)
const lane = requestUpdateLane(fiber);

// 2. 创建更新对象
const update = {
    lane,
    action, // 更新函数或值
    hasEagerState: false, // 是否已计算急切的state
    eagerState: null, // 计算出的state
    next: null, // 指向下一个更新
};

// 3. 将更新加入队列
if (isRenderPhaseUpdate(fiber)) {
    // 如果当前 fiber 正在更新,就跳过本次更新,等到接下来一起更新就可以了
    enqueueRenderPhaseUpdate(queue, update);
} else {
    // 常规更新
    enqueueUpdate(fiber, queue, update, lane);

// 4. 性能优化:提前计算新状态
const alternate = fiber.alternate;
if (
fiber.lanes === NoLanes &&
(alternate === null || alternate.lanes === NoLanes)
) {
    // 获取当前状态
    const currentState = queue.lastRenderedState;
    const eagerState = lastRenderedReducer(currentState, action);

    // 保存计算结果
    update.hasEagerState = true;
    update.eagerState = eagerState;

    // 5. 如果状态未变化,则跳过更新
    if (is(eagerState, currentState)) {
        return;
    }
}

// 6. 调度更新
const eventTime = requestEventTime();
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
}
}

3. 核心流程解析

a) 更新对象(Update)结构

每次调用 setState 都会创建一个更新对象:

javascript 复制代码
const update = {

    lane: Lane, // 更新优先级

    action: Function|any, // 更新函数或新状态值

    hasEagerState: false, // 是否已预计算

    eagerState: any, // 预计算的状态

    next: Update|null // 指向下一个更新

};

b) 更新队列(Queue)结构

每个 Hook 的更新对象会组成一个环形链表:

javascript 复制代码
const queue = {

    pending: Update, // 指向最后一个更新(形成环形)

    dispatch: Function, // setState 函数

    lastRenderedReducer: Function, // 状态计算函数

    lastRenderedState: any // 上一次渲染的状态值

};

c) 更新入队逻辑

javascript 复制代码
function enqueueUpdate(queue, update) {

    if (queue.pending === null) {

        // 第一个更新:形成自环

        update.next = update;

    } else {

        // 插入到环形链表尾部

        update.next = queue.pending.next;

        queue.pending.next = update;

    }

    queue.pending = update; // 始终指向最新的更新

}

d) 环形链表示例

环形链表在React更新队列中的设计

注意:环形链表中,pending指向最新更新(U3),而U3的next指向链表头部(U1),尾部(U2)指向U3。

合并队列时的优势

在更新过程中,React需要将当前未处理的更新(pending queue)合并到基础队列(baseQueue)中。这里,环形链表的优势在于可以高效地完成两个队列的合并。

为什么环形链表方便合并?

  1. 快速定位头尾:在环形链表中,通过一个节点(pending指针指向的节点)可以立即得到头(pending.next)和尾(pending)。

  2. 高效连接:合并两个环形链表只需要修改两个指针

实际例子

假设当前baseQueue是:B1 -> B2 -> B1 (环形,baseQueue指针指向B2)

pendingQueue是:U1 -> U2 -> U3 -> U1 (环形,pendingQueue指针指向U3)

合并后:

  1. 将baseQueue的尾(B2)与pendingQueue的头(U1)连接:B2.next = U1

  2. 将pendingQueue的尾(U3)与baseQueue的头(B1)连接:U3.next = B1

形成新环形链表:B1 -> B2 -> U1 -> U2 -> U3 -> B1

同时,baseQueue指针指向U3(即最后一个更新)。

这样,我们就将pendingQueue中的更新追加到了baseQueue的尾部。

疑问: dispatchAction 的性能优化与ensureRootIsScheduled 有何不同?

1. dispatchAction 层级:更新生产层

scss 复制代码
function dispatchAction(fiber, queue, action) {
  const update = createUpdate(action); // 1. 创建update
  enqueueUpdate(queue, update);        // 2. 加入队列
  
  if (fiberNotUpdating(fiber)) {       // 3. 判断是否需调度
    scheduleUpdateOnFiber(fiber);      // 4. 发起调度请求
  }
}
  • 核心作用:处理单个状态更新请求

  • 批量相关逻辑

    • 条件跳过:当 fiber 已在更新中时阻止重复调度
    • 队列合并:将连续更新收集到同一队列(如多次 setState)

2. ensureRootIsScheduled 层级:更新消费层

scss 复制代码
function ensureRootIsScheduled(root) {
  // 1. 检查现有任务
  if (existingTask !== null) {
    // 2. 决策:复用/替换/跳过任务
    if (newPriority === existingPriority) {
      return; // 已有相同任务 → 跳过
    }
    cancelTask(existingTask); // 取消低优先级任务
  }
  
  // 3. 创建新任务(批量处理所有待处理更新)
  scheduleCallback(priority, performSyncWorkOnRoot);
}
  • 核心作用:协调整个应用级别的更新执行

  • 批量相关逻辑

    • 任务去重:合并同一优先级的多个调度请求
    • 优先级仲裁:决定哪些更新需要立即处理/批量延迟
    • 时间切片:将多个 root 更新合并到单个渲染周期

场景模拟:多组件同时更新

scss 复制代码
// 组件A
const [a, setA] = useState(0);
setA(1); // 触发dispatchAction

// 组件B (不同fiber)
const [b, setB] = useState(0);
setB(1); // 触发dispatchAction

处理流程:

  1. dispatchAction 层

    • 组件A:创建update → 加入队列 → 发起调度请求
    • 组件B:创建update → 加入队列 → 发起调度请求
  2. ensureRootIsScheduled 层

  • 关键决策:将两个独立请求合并为单个渲染任务
问题 dispatchAction解决 ensureRootIsScheduled解决
单组件多次更新 队列合并(防止多次调度) 不涉及(已在fiber层合并)
多组件更新冲突 无法解决 跨fiber任务仲裁
优先级抢占 无处理能力 取消低优任务,执行高优任务
时间切片利用 无法优化 合并多个root更新到单次渲染循环
渲染帧率稳定 无控制 通过任务分片保障60fps

总结:为什么需要双层设计?

  1. 关注点分离

    • 微观:dispatchAction 处理单个更新原子操作
    • 宏观:ensureRootIsScheduled 协调系统级更新策略
相关推荐
掘金011 分钟前
震惊!Vue3 竟能这样写?React 开发者狂喜的「Vue-React 缝合怪」封装指南
javascript·vue.js·react.js
Lsx_2 分钟前
分不清RAG 、Function Call、MCP、Agent?一文秒懂它们的区别和联系
前端·agent·mcp
毕了业就退休23 分钟前
websocket 的心跳机制你知道几种
前端·javascript·http
子林super24 分钟前
aiforcast集群单节点CPU使用率100%问题
前端
CF14年老兵25 分钟前
为什么 position: absolute 在 Flexbox 里会失效?
前端·css·trae
xianxin_29 分钟前
CSS 选择器
前端
徐小夕31 分钟前
花3个月时间,写了一款协同文档编辑器
前端·vue.js·算法
Nicholas6838 分钟前
flutter滚动视图之ScrollDirection、ViewportOffset源码解析(一)
前端
Dream耀1 小时前
FitKick 电商APP项目总结二
前端·javascript·react.js
ZsTs1191 小时前
一篇通关:从 MVVM 到渲染优化,Vue 基础核心 5 大模块全解析
前端·vue.js·面试