目录
updateQueue总览--对前文中所提到的update做详细介绍
首先说明一下在updateQueue中所提到的update类型所包含的具体结构。
export type Update<State> = {|
// TODO: Temporary field. Will remove this by storing a map of
// transition -> event time on the root.
eventTime: number,
lane: Lane,
tag: 0 | 1 | 2 | 3,
payload: any,
callback: (() => mixed) | null,
next: Update<State> | null,
|};
这其中eventTime储存时间,是临时属性。lane代表优先级。tag为标签,表明update的类型。payload为有效载荷。callback为更新的回调函数。next为下一个update任务,体现了其有序的队列结构。
此处给出updateQueue的源代码
export type UpdateQueue<State> = {|
baseState: State,
firstBaseUpdate: Update<State> | null,
lastBaseUpdate: Update<State> | null,
shared: SharedQueue<State>,
effects: Array<Update<State>> | null,
|};
其中base存储为更新时的状态,firstBaseUpdate为优先级不够的第一个节点,lastBaseUpdate为优先级不够的最后一个节点,(这里可能会需要链表第二次遍历才能够处理),shared中含有pending节点以及优先级。
为了动态展现updateQueue的结构,这里分析enqueue的代码
export function enqueueUpdate<State>(
fiber: Fiber,
update: Update<State>,
lane: Lane,
) {
const updateQueue = fiber.updateQueue;
if (updateQueue === null) {
// Only occurs if the fiber has been unmounted.
return;
}
const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;
if (isInterleavedUpdate(fiber, lane)) {
const interleaved = sharedQueue.interleaved;
if (interleaved === null) {
// This is the first update. Create a circular list.
update.next = update;
// At the end of the current render, this queue's interleaved updates will
// be transferred to the pending queue.
pushInterleavedQueue(sharedQueue);
} else {
update.next = interleaved.next;
interleaved.next = update;
}
sharedQueue.interleaved = update;
} else {
const pending = sharedQueue.pending;
if (pending === null) {
// This is the first update. Create a circular list.
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
sharedQueue.pending = update;
}
}
这其中,会对更新的节点进行分析是否是不同的根节点,大多数react应用只有一个根节点,所以进入一般情况。首先判断pending是否为空,空就将update作为第一个节点。假如不为空,就将update插入,插入位置为上次pending节点的next位置,然后将pending指针赋给当前新update节点,事实上,作为一个循环链表,当前的pending节点为最新更新,显然,pending节点的next就为首次更新节点。
接下来我要给出updateQueue的处理过程,也就是processUpdateQueue函数。
export function processUpdateQueue<State>(
workInProgress: Fiber,
props: any,
instance: any,
renderLanes: Lanes,
): void {
// This is always non-null on a ClassComponent or HostRoot
const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
hasForceUpdate = false;
if (__DEV__) {
currentlyProcessingQueue = queue.shared;
}
let firstBaseUpdate = queue.firstBaseUpdate;
let lastBaseUpdate = queue.lastBaseUpdate;
// Check if there are pending updates. If so, transfer them to the base queue.
let pendingQueue = queue.shared.pending;
if (pendingQueue !== null) {
queue.shared.pending = null;
// The pending queue is circular. Disconnect the pointer between first
// and last so that it's non-circular.
const lastPendingUpdate = pendingQueue;
const firstPendingUpdate = lastPendingUpdate.next;
lastPendingUpdate.next = null;
// Append pending updates to base queue
if (lastBaseUpdate === null) {
firstBaseUpdate = firstPendingUpdate;
} else {
lastBaseUpdate.next = firstPendingUpdate;
}
lastBaseUpdate = lastPendingUpdate;
// If there's a current queue, and it's different from the base queue, then
// we need to transfer the updates to that queue, too. Because the base
// queue is a singly-linked list with no cycles, we can append to both
// lists and take advantage of structural sharing.
// TODO: Pass `current` as argument
const current = workInProgress.alternate;
if (current !== null) {
// This is always non-null on a ClassComponent or HostRoot
const currentQueue: UpdateQueue<State> = (current.updateQueue: any);
const currentLastBaseUpdate = currentQueue.lastBaseUpdate;
if (currentLastBaseUpdate !== lastBaseUpdate) {
if (currentLastBaseUpdate === null) {
currentQueue.firstBaseUpdate = firstPendingUpdate;
} else {
currentLastBaseUpdate.next = firstPendingUpdate;
}
currentQueue.lastBaseUpdate = lastPendingUpdate;
}
}
}
// These values may change as we process the queue.
if (firstBaseUpdate !== null) {
// Iterate through the list of updates to compute the result.
let newState = queue.baseState;
// TODO: Don't need to accumulate this. Instead, we can remove renderLanes
// from the original lanes.
let newLanes = NoLanes;
let newBaseState = null;
let newFirstBaseUpdate = null;
let newLastBaseUpdate = null;
let update = firstBaseUpdate;
do {
const updateLane = update.lane;
const updateEventTime = update.eventTime;
if (!isSubsetOfLanes(renderLanes, updateLane)) {
// Priority is insufficient. Skip this update. If this is the first
// skipped update, the previous update/state is the new base
// update/state.
const clone: Update<State> = {
eventTime: updateEventTime,
lane: updateLane,
tag: update.tag,
payload: update.payload,
callback: update.callback,
next: null,
};
if (newLastBaseUpdate === null) {
newFirstBaseUpdate = newLastBaseUpdate = clone;
newBaseState = newState;
} else {
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}
// Update the remaining priority in the queue.
newLanes = mergeLanes(newLanes, updateLane);
} else {
// This update does have sufficient priority.
if (newLastBaseUpdate !== null) {
const clone: Update<State> = {
eventTime: updateEventTime,
// This update is going to be committed so we never want uncommit
// it. Using NoLane works because 0 is a subset of all bitmasks, so
// this will never be skipped by the check above.
lane: NoLane,
tag: update.tag,
payload: update.payload,
callback: update.callback,
next: null,
};
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}
// Process this update.
newState = getStateFromUpdate(
workInProgress,
queue,
update,
newState,
props,
instance,
);
const callback = update.callback;
if (
callback !== null &&
// If the update was already committed, we should not queue its
// callback again.
update.lane !== NoLane
) {
workInProgress.flags |= Callback;
const effects = queue.effects;
if (effects === null) {
queue.effects = [update];
} else {
effects.push(update);
}
}
}
update = update.next;
if (update === null) {
pendingQueue = queue.shared.pending;
if (pendingQueue === null) {
break;
} else {
// An update was scheduled from inside a reducer. Add the new
// pending updates to the end of the list and keep processing.
const lastPendingUpdate = pendingQueue;
// Intentionally unsound. Pending updates form a circular list, but we
// unravel them when transferring them to the base queue.
const firstPendingUpdate = ((lastPendingUpdate.next: any): Update<State>);
lastPendingUpdate.next = null;
update = firstPendingUpdate;
queue.lastBaseUpdate = lastPendingUpdate;
queue.shared.pending = null;
}
}
} while (true);
if (newLastBaseUpdate === null) {
newBaseState = newState;
}
queue.baseState = ((newBaseState: any): State);
queue.firstBaseUpdate = newFirstBaseUpdate;
queue.lastBaseUpdate = newLastBaseUpdate;
// Interleaved updates are stored on a separate queue. We aren't going to
// process them during this render, but we do need to track which lanes
// are remaining.
const lastInterleaved = queue.shared.interleaved;
if (lastInterleaved !== null) {
let interleaved = lastInterleaved;
do {
newLanes = mergeLanes(newLanes, interleaved.lane);
interleaved = ((interleaved: any).next: Update<State>);
} while (interleaved !== lastInterleaved);
} else if (firstBaseUpdate === null) {
// `queue.lanes` is used for entangling transitions. We can set it back to
// zero once the queue is empty.
queue.shared.lanes = NoLanes;
}
// Set the remaining expiration time to be whatever is remaining in the queue.
// This should be fine because the only two other things that contribute to
// expiration time are props and context. We're already in the middle of the
// begin phase by the time we start processing the queue, so we've already
// dealt with the props. Context in components that specify
// shouldComponentUpdate is tricky; but we'll have to account for
// that regardless.
markSkippedUpdateLanes(newLanes);
workInProgress.lanes = newLanes;
workInProgress.memoizedState = newState;
}
if (__DEV__) {
currentlyProcessingQueue = null;
}
}
接下来进行分析,首先将queue,firstBaseUpdate,lastBaseUpdate以及将三个变量存住,然后判断pendingQueue,假如队列不为空,那就先得到链表中的第一个与最后一个节点,然后将环断开得到一个单线的链表,接下来判断firstBaseUpdate与lastBaseUpdate,假如lastBaseUpdate为空,那就将fisrtBaseUpdate与lastBaseUpdate直接赋值给链表的头部与尾部,假如并非空,那就将lastBaseUpdate的下一个更新节点赋值给链表的头节点。为了保证current节点的一致性,会对current节点做出相似的操作。然后初始化若干变量,包括newState,newLanes,newBaseState等,循环开始,判断当前更新是否满足优先级,如果不满足,那就创建一个clone节点同时判断newLastBaseUpdate是否为空值,假如为空值,说明尚未有优先度不足的节点,那就将当前节点作为第一个节点,将newFirstBaseUpdate以及newLastBaseUpdate赋值为当前节点。假如并非空值,那就将newLastBaseUpdate指向该节点,延长队列,并且提高update的优先级。如果当前更新满足优先级,那就检测newLastBaseUpdate是否为空值,假如并非空值,说明已经出现需要延迟的更新,那就需要将newLastBaseUpdate指向该更新节点,延长队列。然后处理这次更新,调用了getStateFromUpdate,然后检测是否有回调函数,有就标记节点中含有CallBack标签并且将回调函数汇总于effects中。然后寻找下一个更新对象,当更新对象为null时,假如pendingQueue为空则循环处理结束,否则再次进行拆环遍历,当循环结束时检测 newLastBaseUpdate是否为空,为空说明更新已经完成,将更新的newState赋值给newBaseState。将BaseState进行保存,同时保存延迟的更新节点与相关优先级,同时更新memoizedState为newState。
大致介绍了updateQueue的概念,数据结构以及其中的具体的处理流程。