React Update Queue 源码全链路解析:从 setState 到 DOM 更新

前言

setStatedispatch 在 React 中看似只是一次简单的状态更新,但在 Fiber 架构的内部,它会触发一整套 Update Queue + 调度器 的协作机制,最终才会完成 DOM 更新。

本文将结合源码变量、关键函数和数据结构,完整拆解 React Update Queue 的内部工作流程。


1. Update Queue 在哪里?

在 Fiber 架构中,每个组件实例对应一个 Fiber 节点 ,这个节点有一个 updateQueue 属性,用来存放等待执行的更新任务。

结构示例(简化版):

yaml 复制代码
fiber.updateQueue = {
  baseState: initialState,      // 本轮更新前的状态
  firstBaseUpdate: null,        // 队列起点
  lastBaseUpdate: null,         // 队列终点
  shared: {
    pending: null,              // 待处理的循环链表(新 update 插这里)
  },
  effects: null                  // 额外副作用
};

特点:

  • 每个 Fiber 节点都有自己的 updateQueue
  • 队列采用 循环链表 存储 update
  • 支持批量合并和不同优先级(lane)

2. 创建 Update 对象

当我们调用:

scss 复制代码
setState(newValue)

或者:

bash 复制代码
dispatch({ type: 'add' })

React 会调用 createUpdate(位于 ReactUpdateQueue.new.js)创建一个 Update 对象:

yaml 复制代码
const update = {
  lane: lane,        // 优先级
  action: action,    // setState 传入的值或函数
  eagerReducer: null,
  eagerState: null,
  next: null         // 链表指针
};

注意

  • lane 是调度优先级的关键
  • action 可能是新值,也可能是 (prevState) => newState

3. 插入 Update 到队列

创建好的 update 会被 enqueueUpdate(位于 ReactUpdateQueue.new.js)放入当前 Fiber 的 updateQueue.shared.pending 中:

ini 复制代码
const pending = queue.shared.pending;
if (pending === null) {
  update.next = update; // 第一个节点,自己指向自己
} else {
  update.next = pending.next;
  pending.next = update;
}
queue.shared.pending = update; // 更新尾指针

循环链表的好处:

  • 插入和合并更新都很高效
  • 新 update 可以快速与旧 update 合并

4. 调度更新

队列更新后,React 会调用:

scss 复制代码
scheduleUpdateOnFiber(fiber, lane, eventTime);

这个函数(位于 ReactFiberWorkLoop.new.js)会:

  • 标记当前 Fiber 对应的 FiberRoot 有更新(markRootUpdated
  • 把更新请求交给调度器(Scheduler)

调度器的决定:

  • 同步模式:立即渲染
  • 并发模式:可能延迟,合并更多更新再渲染

5. 渲染阶段处理队列

当 Fiber 进入 Render 阶段

  1. baseState 开始
  2. 遍历 update 链表(processUpdateQueue
  3. 调用 applyUpdate 将每个 update 应用到当前 state
  4. 得到新的 memoizedState

核心伪代码:

ini 复制代码
let newState = baseState;
let update = firstBaseUpdate;
do {
  newState = applyUpdate(update, newState);
  update = update.next;
} while (update !== null);

6. Commit 阶段

Render 阶段算出新 state 后,进入 Commit 阶段:

  1. 更新 Fiber 的 memoizedState
  2. 执行 DOM 更新
  3. 执行副作用(useEffect 等)
  4. 完成渲染

7. 源码变量标注版流程图

scss 复制代码
┌───────────────────────────────┐
│  setState / dispatch(action)  │
│  (action: 值或函数)            │
└──────────────┬────────────────┘
               │
               ▼
     ┌────────────────────────────┐
     │ createUpdate(action, lane) │
     │ ReactUpdateQueue.new.js    │
     │ { lane, action, next: null }│
     └─────────────┬──────────────┘
                   │
                   ▼
┌──────────────────────────────────────────┐
│ enqueueUpdate(fiber.updateQueue, update) │
│ queue = fiber.updateQueue.shared         │
│ pending = queue.pending                  │
│ 插入循环链表 → queue.pending = update     │
└────────────────┬─────────────────────────┘
                 │
                 ▼
       ┌─────────────────────────────────┐
       │ scheduleUpdateOnFiber(fiber, lane) │
       │ ReactFiberWorkLoop.new.js        │
       │ 标记 FiberRoot 脏 → 调度执行      │
       └────────────────┬─────────────────┘
                        │
                        ▼
             ┌────────────────────────────┐
             │ performConcurrentWorkOnRoot │
             │ performSyncWorkOnRoot       │
             │ 根据 lane 决定执行方式      │
             └────────────┬───────────────┘
                          │
                          ▼
         ┌───────────────────────────────────┐
         │ renderRootSync / renderRootConcurrent │
         │ processUpdateQueue(queue)           │
         │ baseState → 遍历 update 链表         │
         │ newState = applyUpdate(update, baseState) │
         │ fiber.memoizedState = newState     │
         └──────────────────┬────────────────┘
                            │
                            ▼
              ┌─────────────────────────────┐
              │ commitRoot(root)             │
              │ 更新 DOM & 执行副作用         │
              │ Fiber.memoizedState ← newState │
              └─────────────────────────────┘

8. 源码关键函数速查

阶段 文件 函数
创建 Update ReactUpdateQueue.new.js createUpdate
插入队列 ReactUpdateQueue.new.js enqueueUpdate
调度更新 ReactFiberWorkLoop.new.js scheduleUpdateOnFiber
处理队列 ReactUpdateQueue.new.js processUpdateQueue
应用更新 ReactUpdateQueue.new.js applyUpdate
渲染执行 ReactFiberWorkLoop.new.js renderRootSync / renderRootConcurrent
提交更新 ReactFiberWorkLoop.new.js commitRoot

总结

  • Update Queue 是 Fiber 节点的更新容器,用循环链表存储 update
  • Update 对象 保存了更新动作和优先级
  • 调度器 根据 lane 决定执行时机
  • processUpdateQueue 渲染阶段批量计算新 state
  • commitRoot 执行实际 DOM 更新和副作用

理解这个机制后,你在调试 React 源码或分析性能瓶颈时,就能快速定位更新的来源、队列状态和调度时机。

相关推荐
dly_blog13 小时前
Vue 响应式陷阱与解决方案(第19节)
前端·javascript·vue.js
消失的旧时光-194313 小时前
401 自动刷新 Token 的完整架构设计(Dio 实战版)
开发语言·前端·javascript
console.log('npc')13 小时前
Table,vue3在父组件调用子组件columns列的方法展示弹窗文件预览效果
前端·javascript·vue.js
用户479492835691513 小时前
React Hooks 的“天条”:为啥绝对不能写在 if 语句里?
前端·react.js
我命由我1234513 小时前
SVG - SVG 引入(SVG 概述、SVG 基本使用、SVG 使用 CSS、SVG 使用 JavaScript、SVG 实例实操)
开发语言·前端·javascript·css·学习·ecmascript·学习方法
用户479492835691514 小时前
给客户做私有化部署,我是如何优雅搞定 NPM 依赖管理的?
前端·后端·程序员
C_心欲无痕14 小时前
vue3 - markRaw标记为非响应式对象
前端·javascript·vue.js
qingyun98914 小时前
深度优先遍历:JavaScript递归查找树形数据结构中的节点标签
前端·javascript·数据结构
熬夜敲代码的小N15 小时前
Vue (Official)重磅更新!Vue Language Tools 3.2功能一览!
前端·javascript·vue.js
90后的晨仔15 小时前
用 Python 脚本一键重命名序列帧图片的名称
前端