14_React 中的更新队列 updateQueue

一、概述

updateQueue 是挂在 Fiber / Hook 上的更新队列(链表),用于缓存 setState 产生的 update,并在 render 阶段按优先级(lane)依次计算出新的 state。

在 React 中,有许多触发状态更新的方法,比如:

  • ReactDOM.createRoot
  • setState
  • useState dispatcher
  • useReducer dispatcher

这些方法使用相同的更新流程,因为它们都使用 updateQueue 这个数据结构。

二、两套 updateQueue

React 里有两套队列:

1️⃣ 类组件

TypeScript 复制代码
fiber.updateQueue = {
  baseState, // 初始 state,update 基于该 state 计算新的 state
  firstBaseUpdate, // 更新前该 FiberNode 中已保存的 update 链表,表头为 firstBaseUpdate
  lastBaseUpdate, // 链表尾部为 lastBaseUpdate
  // 触发更新后,产生的 update 会保存在 shared.pending 中形成单向环状链表
  // 计算 state 时,该环状链表会被拆分并拼接在 lastBaseUpdate 后面。
  shared: {
    pending
  }
}

2️⃣ 函数组件 Hooks

每个 useState / useReducer 都有一个 queue:

TypeScript 复制代码
hook.queue = {
  pending: Update | null, // 环形链表
  dispatch: Function
}

三、Update 数据结构

Update 节点

TypeScript 复制代码
type Update = {
  lane: Lane;        // 优先级
  action: any;       // setState 传入的值/函数
  next: Update | null;
}

队列结构(环形链表)

Plain 复制代码
pending
   ↓
update1 → update2 → update3
   ↑                 ↓
   ← ← ← ← ← ← ← ← ←

为什么是环形?

  • O(1) 插入
  • 不需要区分头尾

四、dispatch(setState)发生了什么?

JavaScript 复制代码
setCount(c => c + 1)
TypeScript 复制代码
function dispatchSetState(fiber, queue, action) {
  const update = {
    lane: requestUpdateLane(), // 分配优先级
    action,
    next: null
  };

  enqueueUpdate(queue, update);

  scheduleUpdateOnFiber(fiber);
}

enqueueUpdate

TypeScript 复制代码
function enqueueUpdate(queue, update) {
  const pending = queue.pending;

  if (pending === null) {
    update.next = update;
  } else {
    update.next = pending.next;
    pending.next = update;
  }

  queue.pending = update;
}

插入效果:永远插在"尾部",但保持环形。

五、render 阶段:如何消费 updateQueue?

TypeScript 复制代码
processUpdateQueue()

执行流程

TypeScript 复制代码
let newState = baseState;

let update = firstUpdate;

do {
  if (lane 满足当前优先级) {
    newState = reducer(newState, update.action);
  } else {
    // 跳过(并发关键)
    // 执行在下一次 render 开始的时候,和下一 render 的 updates 组成新的链表
  }

  update = update.next;
} while (update !== null);

reducer 本质

TypeScript 复制代码
function reducer(state, action) {
  return typeof action === 'function'
    ? action(state)
    : action;
}

六、baseState & baseQueue(并发核心)

React 19 支持并发:低优先级更新可能被跳过

TypeScript 复制代码
hook.memoizedState // 当前 state

hook.baseState     // 上一次稳定 state

hook.baseQueue     // 未处理的 update

执行逻辑

Plain 复制代码
高优先级 → 先执行
低优先级 → 留在 baseQueue

示例

JavaScript 复制代码
setCount(1)        // 高优先级
startTransition(() => {
  setCount(2)      // 低优先级
})

render 结果:

Plain 复制代码
先执行 1
2 留在队列,下次再算

七、lane(优先级系统)

React 19 的核心:

Plain 复制代码
每个 update 都有 lane(优先级)

判断逻辑

TypeScript 复制代码
if ((update.lane & renderLanes) !== 0) {
  // 执行
} else {
  // 跳过
}

不同的 update 是有不同的优先级,高优先级的 update 能够中断低优先级的 update,当高优先级的 update 完成更新之后,后续的低优先级更新会在高优先级 update 更新后的 state 的基础上再来进行更新。

八、批处理(Batching)

React 18+ 自动 batching

JavaScript 复制代码
setCount(1)
setCount(2)

结果:

Plain 复制代码
只触发一次 render

因为 多个 update 进入同一个 queue。

执行顺序:

Plain 复制代码
1 → 2 → 最终 state = 2

错误写法:

JavaScript 复制代码
setCount(count + 1)
setCount(count + 1)

结果:+1(不是 +2)

正确写法:

JavaScript 复制代码
setCount(c => c + 1)
setCount(c => c + 1)

原因:每个 update 都基于"上一个结果"

九、和 effectQueue 的区别

本质:

Plain 复制代码
updateQueue → render 阶段
effectQueue → commit 阶段

十、完整流程总结

Plain 复制代码
setState
   ↓
创建 update(带 lane)
   ↓
加入 updateQueue(环形链表)
   ↓
scheduleUpdateOnFiber
   ↓
render 阶段:
   ↓
processUpdateQueue(计算 state)
   ↓
commit 阶段:
   ↓
更新 DOM + 执行 effect
相关推荐
前端 贾公子1 小时前
解决浏览器端 globalThis is not defined 报错
前端·javascript·vue.js
宁雨桥1 小时前
前端与AI结合实战分享
前端·人工智能
研究点啥好呢2 小时前
快手多模态算法工程师面试题精选:10道高频考题+答案解析
java·开发语言·人工智能·ai·面试·笔试
之歆2 小时前
DAY12_CSS3选择器全攻略 + 盒子新特性完全指南(下)
前端·javascript·css3
kyriewen112 小时前
代码写成一锅粥?3个设计模式让你的项目“起死回生”
开发语言·前端·javascript·设计模式·ecmascript
光影少年2 小时前
react函数组件、类组件、纯组件、受控/非受控组件
前端·react.js·掘金·金石计划
程序员包打听2 小时前
MoonBit 是什么?给第一次听说这门语言的你
前端·后端
Rkgua2 小时前
CSS动画效果
前端·css
Rkgua2 小时前
Flexbox 与 Grid 布局
前端·css