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 小时前
Python 函数核心:位置参数与关键字参数详解
java·前端·python
丷丩1 小时前
MapLibre GL JS第8课:禁用滚动缩放
javascript·mapbox·maplibre gl js
Kratzdisteln1 小时前
【无标题】
前端·python
李剑一1 小时前
小红书前端架构面试问的挺深入啊!面试官:Vue中组合式API与选项式API的设计权衡
vue.js·面试
Curvatureflight1 小时前
前端国际化 i18n 落地实践:语言包、动态文案和格式化问题怎么处理?
前端·c++·vue
kTR2hD1qb2 小时前
Claude Code Skill的介绍与使用
java·前端·数据库·人工智能
better_liang2 小时前
每日Java面试场景题知识点之-如何设计分布式锁
java·redis·zookeeper·面试·分布式锁
修己xj3 小时前
打造专属博文封面神器:一个开源免费的博文封面生成器ThisCover
前端
kyriewen3 小时前
面试8家前端岗位后,我发现了一个残酷的事实:AI不是加分项,是门槛
前端·javascript·面试
用户887665426633 小时前
Git 和 GitHub 入门:从版本控制到团队协作,一篇文章讲清楚
面试·github