processUpdateQueue源码解析

521 阅读5分钟

processUpdateQueue函数的作用是: 计算update, 得到state.

对于class组件, setState(...)会参数一个update, state就是class的state, 代表ui的状态.

我将一次完整的渲染任务划分为如下阶段:

创建更新 - 调度fiber - render阶段 - commit阶段 - 渲染到屏幕

processUpdateQueue函数是在render阶段被调用, 所有我又粗略的将它划分为:

processUpdateQueue前 - processUpdateQueue中 - processUpdateQueue后

如下图所示:

20220419103721

前置知识

UpdateQueue数据结构

fiber对象中有一个属性fiber.updateQueue, updateQueuefiber对象的一个属性, 所以不能脱离fiber存在. 它们之间数据结构和引用关系如下:

20220418155053

可以看到数据结构updateQueue主要由两个链表组成: 由shared.pending指向的等待队列(pending queue)和由firstBaseUpdate``lastBaseUpdate指向的基础队列(base queue). 逻辑上, 我们应该将数据结构updateQueue看成一个列表: base queue + pending queue, 如下图所示:

20220418160126

注意:

  • pendingQueue是一个循环链表, fiber.updatQueue.shared.pending指针指向pendingQueue的最后一项.

processUpdateQeueue函数使用的一些变量及说明

  • queue: 处理中队列, workInProgress fiber上的updateQueue.
  • currentQueue: 当前队列, current fiber上的updateQueue.

current fiber表示当前页面所呈现的状态, 老的fiber对象.

  • firstPendingUpdate: queuepending queue的第一个update.
  • lastPendingUpdate: queuepending queue的最后一个update.

queue为处理中更新队列, pending queue为更新队列的等待队列, 参看上文的UpdateQueue数据结构.

  • firstBaseUpdate: queuebase queue的第一个update.
  • lastBaseUpdate: queuebase queue的最后一个update.

processUpdateQueue前

我们通过克隆 当前队列 来创建一个新的处理中队列.

下面是我截取的从performUnitOfWorkprocessUpdateQueue的函数调用堆栈:

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中

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:

20220418165118

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树, 也意味着queuecurrentQueue的切换. 而且我们还会检测queuebase queue是否处理完, 如果没有处理完, 还会发起再一次的渲染任务.

如果渲染任务被打断, 则不会执行以上操作.