UpdateQueue
简介
React
节点状态存储在fiber
的memoizedState
属性当中,而老fiber
节点到新fiber
节点的更新操作则存储在fiber
的updateQueue
属性中。不同类型节点的updateQueue
存储内容格式不相同,所对应的功能也有所差异。具体如下:
class
组件主要处理state
的更新。- 函数组件主要处理
effect
的create、destroy
回调函数。 - 普通标签主要处理属性的变动。
ClassComponent/HostRoot
ClassComponent
和HostRoot
两者更新的相关代码都存放在react-reconciler
包的ReactUpdateQueue.new.js
文件当中。下面分别介绍比较重要的几个函数。
initializeUpdateQueue
export function initializeUpdateQueue<State>(fiber: Fiber): void {
const queue: UpdateQueue<State> = {
// 初始化状态
baseState: fiber.memoizedState,
// 记录第一个更新,与优先级相关
firstBaseUpdate: null,
// 记录最后一个更新,与优先级相关
lastBaseUpdate: null,
// 存放更新的具体内容
shared: {
// 一个或多个更新形成的循环链表
pending: null,
// 如果当前处于 render 阶段,此时产生的更新会放在 interleaved 中
// 在render结束时,interleaved 变为 pending queue
interleaved: null,
// 更新赛道
lanes: NoLanes,
},
// 记录更新内容回调
effects: null,
};
// 更新队列
fiber.updateQueue = queue;
}
该函数主要用于初始化一个更新队列updateQueue
。
createUpdate
export function createUpdate(eventTime: number, lane: Lane): Update<*> {
const update: Update<*> = {
// 更新创建开始时间
eventTime,
// 该更新的 lane
lane,
// 更新的标识符
tag: UpdateState,
// 更新的内容
payload: null,
// 更新的回调
callback: null,
// 指向下一个更新
next: null,
};
return update;
}
createUpdate
主要作用是创建一个更新。
enqueueUpdate
enqueueUpdate
主要作用是将新增的更新(Update
)添加到循环链表中,并存放到updateQueue.shared.pending
上,其核心代码如下:
const pending = sharedQueue.pending;
if (pending === null) {
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
sharedQueue.pending = update;
每次添加完update后,pending都会指向这个最新添加的update。由于是循环链表,所以当前的pending的next指向的是第一个更新。
processUpdateQueue
processUpdateQueue
主要作用是执行updateQueue
来更新state
。第一段代码如下:
if (pendingQueue !== null) {
queue.shared.pending = null;
// 找到最先的 update,然后将最后的一个 update 的 next 断开
const lastPendingUpdate = pendingQueue;
const firstPendingUpdate = lastPendingUpdate.next;
lastPendingUpdate.next = null;
if (lastBaseUpdate === null) {
firstBaseUpdate = firstPendingUpdate;
} else {
lastBaseUpdate.next = firstPendingUpdate;
}
lastBaseUpdate = lastPendingUpdate;
}
这段代码可以用下图来表示:
注意:其中置为null
表示那一条指向是被切断了,也就是不存在该指向了。另外shared.pending是指向最后一次添加的update的。
紧接着就是一个while
循环遍历所有的update
来处理state
。在循环中有一个判断:
if (!isSubsetOfLanes(renderLanes, updateLane)) {}
该判断主要是判断当前更新对应的lane
是否在renderLanes
中,如果不在,那么这次就不应该执行该更新。举一个例子,现在有A,C,B,D
四个更新形成的链表,而当前的renderLanes
只有A,B
两个符合时,此时执行到更新C
的时候,会先克隆一个update
:
const clone: Update<State> = {
eventTime: updateEventTime,
lane: updateLane,
tag: update.tag,
payload: update.payload,
callback: update.callback,
next: null,
};
然后将这个update
形成链表,并且用newFirstBaseUpdate
和newLaseBaseUpdate
表示链表的头尾。
// lane不符合,那么记录下当前的 update
if (newLastBaseUpdate === null) {
newFirstBaseUpdate = newLastBaseUpdate = clone;
newBaseState = newState;
} else {
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}
需要注意的是,一旦C
记录下来了,遍历B
的时候newLastBaseUpdate !== null
,同样会被记录。换句话说,也就是当某一个更新的lane
不符合时,后续所有的更新都会被单独记录下来。
另外一种情况是lane
符合时,此时会进行执行update
,并获取最新的state
。
newState = getStateFromUpdate(...)
getStateFromUpdate
方法会根据update
的tag
和payload
获取新的state
。
随后,将执行了的update
存放到effects
中,以便更新完成时触发其回调。
if (effects === null) {
queue.effects = [update];
} else {
effects.push(update);
}
最后,更新状态后,将对应的属性更新:
queue.baseState = ((newBaseState: any): State);
queue.firstBaseUpdate = newFirstBaseUpdate;
queue.lastBaseUpdate = newLastBaseUpdate;
workInProgress.memoizedState = newState;
注意此时的newState
代表的是执行了所有可执行update
的state
,如执行了A,B
这两个更新。而baseState
表示第一次lane
不符合时,前面的state
,如遇到C
时,lane
不符合,baseState
记录的是A
更新后的state
。
这样的话,在下一轮更新时,由于lastBaseUpdate
存在:
if (lastBaseUpdate === null) {
firstBaseUpdate = firstPendingUpdate;
} else {
lastBaseUpdate.next = firstPendingUpdate;
}
此时会将下一轮的更新于当前轮次跳过的更新合并在一次执行,也就是下一轮在执行更新前,会执行本轮跳过的更新,如B,C,D
这个三个更新。这也是为什么baseState
只记录A
更新的原因了,因为它可以作为下一轮更新的起始state
。
commitUpdateQueue
commitUpdateQueue
比较简单,就是执行已经被执行了的update
的callback
。
for (let i = 0;i < effects.length;i++) {
const effect = effects[i];
const callback = effect.callback;
if (callback !== null) {
effect.callback = null;
callCallback(callback, instance);
}
}
FunctionComponent
函数式组件的updateQueue
与class
组件的差异比较大。它的updateQueue
主要用于存放生命周期的回调函数。找到ReactFiberHooks.new.js
文件的pushEffect
方法,首先会创建一个effect
:
const effect: Effect = {
// 表示 hook 的 tag
tag,
// 创建时的回调函数
create,
// 销毁时的回调函数
destroy,
// hook 的依赖
deps,
// 指向下一个 effect
next: (null: any),
};
随后将该effect
形成循环链表(该链表形式与class
组件类似),放入到updateQueue
的lastEffect
属性当中:
const firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
currentlyRenderingFiber.updateQueue.lastEffect = effect;
HostComponent
HostComponent
的updateQueue
主要记录props
的变化。
const updatePayload = prepareUpdate(
instance,
type,
oldProps,
newProps,
rootContainerInstance,
currentHostContext,
);
workInProgress.updateQueue = (updatePayload: any);
prepareUpdate
方法在react-dom
包中的ReactDOMHostConfig.js
文件中定义:
return diffProperties(
domElement,
type,
oldProps,
newProps,
rootContainerInstance,
);
它主要是调用diffProperties
方法,将新旧props
进行对比,最后将改变了的属性记录成数组形式。其中偶数index
为键,奇数index
为值,结构大致如下:
['name', '张三', 'id', 333, 'style', { color: 'red' }]