processUpdateQueue函数的作用是: 计算update, 得到state.
对于class组件, setState(...)会参数一个update, state就是class的state, 代表ui的状态.
我将一次完整的渲染任务划分为如下阶段:
创建更新 - 调度fiber - render阶段 - commit阶段 - 渲染到屏幕
processUpdateQueue函数是在render阶段被调用, 所有我又粗略的将它划分为:
processUpdateQueue前 - processUpdateQueue中 - processUpdateQueue后
如下图所示:
前置知识
UpdateQueue数据结构
在fiber对象中有一个属性fiber.updateQueue, updateQueue是fiber对象的一个属性, 所以不能脱离fiber存在. 它们之间数据结构和引用关系如下:
可以看到数据结构updateQueue主要由两个链表组成: 由shared.pending指向的等待队列(pending queue)和由firstBaseUpdate``lastBaseUpdate指向的基础队列(base queue). 逻辑上, 我们应该将数据结构updateQueue看成一个列表: base queue + pending queue, 如下图所示:
注意:
pendingQueue是一个循环链表,fiber.updatQueue.shared.pending指针指向pendingQueue的最后一项.
processUpdateQeueue函数使用的一些变量及说明
- queue: 处理中队列,
workInProgress fiber上的updateQueue. - currentQueue: 当前队列,
current fiber上的updateQueue.
current fiber表示当前页面所呈现的状态, 老的fiber对象.
- firstPendingUpdate:
queue的pending queue的第一个update. - lastPendingUpdate:
queue的pending queue的最后一个update.
queue为处理中更新队列, pending queue为更新队列的等待队列, 参看上文的UpdateQueue数据结构.
- firstBaseUpdate:
queue的base queue的第一个update. - lastBaseUpdate:
queue的base queue的最后一个update.
processUpdateQueue前
我们通过克隆 当前队列 来创建一个新的处理中队列.
下面是我截取的从performUnitOfWork到processUpdateQueue的函数调用堆栈:
processUpdateQueue
updateClassInstance
updateClassComponent
beginWork
performUnitOfWork
其中克隆 当前队列 的逻辑在在updateClassInstance中:
function updateClassInstance(){
cloneUpdateQueue(current, workInProgress);
processUpdateQueue(workInProgress, newProps, instance, renderLanes);
}
function cloneUpdateQueue(
current,
workInProgress,
) {
// Clone the update queue from current. Unless it's already a clone.
const queue = workInProgress.updateQueue;
const currentQueue = current.updateQueue;
if (queue === currentQueue) {
const clone = {
baseState: currentQueue.baseState,
firstBaseUpdate: currentQueue.firstBaseUpdate,
lastBaseUpdate: currentQueue.lastBaseUpdate,
shared: currentQueue.shared,
effects: currentQueue.effects,
};
workInProgress.updateQueue = clone;
}
}
processUpdateQueue中

1. 输入参数
props和instance可以略过, 它们只是用在state计算中.
workInProgress
workInProgress: 处理中fiber节点.
renderLanes
renderLanes: 渲染优先级.
每一次render之前, 首先要确定本次render的优先级.
当再渲染阶段处理update队列时, 只有具有足够优先级的update才会生效包含在结果中. 没有足够的优先级update会被跳过, 它被留在队列中, 等到稍后低优先级渲染期间再被处理.
update的优先级update.lane我们称为更新优先级. 我们需要比较渲染优先级(renderLanes)
和更新优先级(update.lane)以确定是否有足够的优先级:
if (!isSubsetOfLanes(renderLanes, updateLane)) {
// 没有足够优先级
} else {
// 有足够优先级
}
2. 将pendingQueue置空
queue.shared.pending = null;
上面的代码将queue和currentQueue的pendingQueue置空, 因为它们共享pendingQueue:
3. 将queue的 pending queue 拼接到 base queue
// pending queue是环状链表, 这步操作是将环剪开
lastPendingUpdate.next = null;
if (lastBaseUpdate === null) {
// base queue 为空
firstBaseUpdate = firstPendingUpdate;
} else {
lastBaseUpdate.next = firstPendingUpdate;
}
lastBaseUpdate = lastPendingUpdate;
4. 将currentQueue的 pending queue 拼接到 base queue
同3的逻辑一样
5. 循环处理 base queue
拿更新优先级(update.lane)和渲染优先级(renderLanes)做比较, 有足够优先级的update才会包含在结果中,
否则将被跳过, 第一个被跳过的update及后续update会保留在队列中,等到稍后低优先级渲染期间再被处理.
场景一: 所有update都有足够的优先级
所有update都会包含在结果中, 然后base queue会被清空, 因为这次已经处理完了这些update, 下次的渲染任务不需要再处理了.
例: base queue为A1 - B1 - C1, baseState为空, renderLanes为1.
其中数字表示优先级, 数字越小优先级越低; 字母表示update的作用.
循环处理 base queue后结果:
newState: ABC
newBaseState: ABC
newBaseQueue: 空
说明: 这里的newState、newBaseState、newBaseQueue(newFirstBaseUpdate、newLastBaseUpdate维护的队列)来自源码,其中newFirstBaseUpdate、newLastBaseUpdate、newBaseState用来重置queue; newState会赋值给workInProgress.memoizedState, 代表ui的状态:
let newState = queue.baseState;
let newBaseState = null;
let newFirstBaseUpdate = null;
let newLastBaseUpdate = null;
// 循环处理 base queue
do {
...
} while (true)
queue.baseState = newBaseState;
queue.firstBaseUpdate = newFirstBaseUpdate;
queue.lastBaseUpdate = newLastBaseUpdate;
workInProgress.memoizedState = newState;
场景二: 存在有不满足优先级的update
不满足优先级的update会被跳过, 不会包含在结果中; 第一个被跳过的update及其后续update会被保存在base queue中, 留到下次低优先级渲染任务再处理.
例: base queue为A1 - B2 - C1 - D2, baseState为空, renderLanes为2.
循环处理 base queue后结果:
newState: BD // 只有足够优先级的update包含在结果中, 否则跳过
newBaseState: A // updateQueue的baseState: 为处理改更新队列之前的`state`, `update`基于该`state`计算更新后的`state`
newBaseQueue: B2 - C1 - D2 // 第一个跳过的update及后续update保留
在commit阶段, 如果检测到有没有处理完的base queue, 会在发起一次渲染任务(也就是我们说的稍后的低优先级渲染任务), 这时base queue为B2 - C1 - D2, baseState为A, renderLanes为1.
这次循环处理 base queue后结果:
newState: ABCD
newBaseState: ABCD
newBaseQueue:
因为我们按照插入顺序处理update,并且在处理跳过的低优先级update时, 也会重新重建高优先级update,所以无论优先级如何,最终的结果都是确定的. 中间状态可能因系统资源的不同而不同,但最终状态总是相同的.
5. 循环处理 base queue 代码
do {
if (!isSubsetOfLanes(renderLanes, updateLane)) {
// update没有足够优先级
if (newLastBaseUpdate === null) {
// newLastQueue为空的情况加入队列代码
newFirstBaseUpdate = newLastBaseUpdate = update;
newBaseState = newState;
} else {
// newLastQueue不为空的情况加入队列代码
newLastBaseUpdate = newLastBaseUpdate.next = update;
}
} else {
// update有足够优先级
if (newLastBaseUpdate !== null) {
// 如果有被跳过的update, 后续的update都会被加入队列
newLastBaseUpdate = newLastBaseUpdate.next = update;
}
// 只有有足够优先级的update才会被处理
// 5.1 处理update
// 5.2 处理callback
}
update = update.next;
} while (true);
5.1 处理update
5.2 处理callback
如果update包含callback,也就是调用setState传入了第二个参数:
setState(..., callback)
需要设置workInProgress的flags,让它包含Callback标记:
workInProgress.flags |= Callback;
并将该update加入workInProgress.updateQueue.effects列表, 好在commit阶段运行.
5.3
这段代码对应的场景是: 在处理update的过程中产生了新的update.
比如在setState中又调用了setState:
setState(function(state, props){
setState(...)
})
processUpdateQueue后
processUpdateQueue后, 正常情况下我们会切换workInProgress树到current树, 也意味着queue和currentQueue的切换. 而且我们还会检测queue上base queue是否处理完, 如果没有处理完, 还会发起再一次的渲染任务.
如果渲染任务被打断, 则不会执行以上操作.