一、概述
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