初窥React-8 (scheduleUpdateOnFiber方法-1)
// Register pending interactions on the root to avoid losing traced interaction data. schedulePendingInteractions(root, lane);
scheduleInteractions
会利用FiberRoot的 pendingInteractionMap
属性和不同的 expirationTime
,获取每次schedule所需的update任务的集合,记录它们的数量,并检测这些任务是否会出错。
function scheduleInteractions(root, lane, interactions) { if (interactions.size > 0) { var pendingInteractionMap = root.pendingInteractionMap; var pendingInteractions = pendingInteractionMap.get(lane); if (pendingInteractions != null) { interactions.forEach(function (interaction) { if (!pendingInteractions.has(interaction)) { // Update the pending async work count for previously unscheduled interaction. interaction.__count++; } pendingInteractions.add(interaction); }); } else { pendingInteractionMap.set(lane, new Set(interactions)); // Update the pending async work count for the current interactions. interactions.forEach(function (interaction) { interaction.__count++; }); } var subscriber = __subscriberRef.current; if (subscriber !== null) { var threadID = computeThreadID(root, lane); subscriber.onWorkScheduled(interactions, threadID); } } }
// root inside of batchedUpdates should be synchronous, but layout updates
// should be deferred until the end of the batch.
performSyncWorkOnRoot(root);
重要方法,SyncLane === lane的时候,其中ensureRootIsScheduled中也调用了performSyncWorkOnRoot.
function performSyncWorkOnRoot(root) { if (!((executionContext & (RenderContext | CommitContext)) === NoContext)) { { throw Error( "Should not already be working." ); } } flushPassiveEffects(); var lanes; var exitStatus; if (root === workInProgressRoot && includesSomeLane(root.expiredLanes, workInProgressRootRenderLanes)) {
//如果workInProgressLanes中有expiredLanes,直接render // There's a partial tree, and at least one of its lanes has expired. Finish // rendering it before rendering the rest of the expired work. lanes = workInProgressRootRenderLanes; exitStatus = renderRootSync(root, lanes); if (includesSomeLane(workInProgressRootIncludedLanes, workInProgressRootUpdatedLanes)) {
// 本次包括了所有正在渲染阶段的lane // The render included lanes that were updated during the render phase. // For example, when unhiding a hidden tree, we include all the lanes // that were previously skipped when the tree was hidden. That set of // lanes is a superset of the lanes we started rendering with. // // Note that this only happens when part of the tree is rendered // concurrently. If the whole tree is rendered synchronously, then there // are no interleaved events. lanes = getNextLanes(root, lanes); exitStatus = renderRootSync(root, lanes); } } else { lanes = getNextLanes(root, NoLanes); exitStatus = renderRootSync(root, lanes); } if (root.tag !== LegacyRoot && exitStatus === RootErrored) { executionContext |= RetryAfterError; // If an error occurred during hydration, // discard server response and fall back to client side render. if (root.hydrate) { root.hydrate = false; clearContainer(root.containerInfo); } // If something threw an error, try rendering one more time. We'll render // synchronously to block concurrent data mutations, and we'll includes // all pending updates are included. If it still fails after the second // attempt, we'll give up and commit the resulting tree. lanes = getLanesToRetrySynchronouslyOnError(root); if (lanes !== NoLanes) { exitStatus = renderRootSync(root, lanes); } } if (exitStatus === RootFatalErrored) { var fatalError = workInProgressRootFatalError; prepareFreshStack(root, NoLanes); markRootSuspended$1(root, lanes); ensureRootIsScheduled(root, now()); throw fatalError; }
//由于这个是sync的,所以不存在插队直接commit
// We now have a consistent tree. Because this is a sync render, we // will commit it even if something suspended. var finishedWork = root.current.alternate; root.finishedWork = finishedWork; root.finishedLanes = lanes; commitRoot(root); // Before exiting, make sure there's a callback scheduled for the next // pending level.
//调度决策的逻辑在ensureRootIsScheduled 函数中, 任务优先级在即将调度的时候去计算,代码在ensureRootIsScheduled函数中
ensureRootIsScheduled(root, now()); return null; }
通过调用getNextLanes去计算在本次更新中应该处理的这批lanes(nextLanes),getNextLanes会调用getHighestPriorityLanes去计算任务优先级。任务优先级计算的原理是这样:更新优先级(update的lane),它会被并入root.pendingLanes,root.pendingLanes经过getNextLanes处理后,挑出那些应该处理的lanes,传入getHighestPriorityLanes
,根据nextLanes找出这些lanes的优先级作为任务优先级。
function getNextLanes(root, wipLanes) { // Early bailout if there's no pending work left. var pendingLanes = root.pendingLanes; if (pendingLanes === NoLanes) { return_highestLanePriority = NoLanePriority; return NoLanes; } var nextLanes = NoLanes; var nextLanePriority = NoLanePriority; var expiredLanes = root.expiredLanes; var suspendedLanes = root.suspendedLanes; var pingedLanes = root.pingedLanes; // Check if any work has expired. if (expiredLanes !== NoLanes) { nextLanes = expiredLanes; nextLanePriority = return_highestLanePriority = SyncLanePriority; } else { // Do not work on any idle work until all the non-idle work has finished, // even if the work is suspended. var nonIdlePendingLanes = pendingLanes & NonIdleLanes; if (nonIdlePendingLanes !== NoLanes) { var nonIdleUnblockedLanes = nonIdlePendingLanes & ~suspendedLanes; if (nonIdleUnblockedLanes !== NoLanes) { nextLanes = getHighestPriorityLanes(nonIdleUnblockedLanes); nextLanePriority = return_highestLanePriority; } else { var nonIdlePingedLanes = nonIdlePendingLanes & pingedLanes; if (nonIdlePingedLanes !== NoLanes) { nextLanes = getHighestPriorityLanes(nonIdlePingedLanes); nextLanePriority = return_highestLanePriority; } } } else { // The only remaining work is Idle. var unblockedLanes = pendingLanes & ~suspendedLanes; if (unblockedLanes !== NoLanes) { nextLanes = getHighestPriorityLanes(unblockedLanes); nextLanePriority = return_highestLanePriority; } else { if (pingedLanes !== NoLanes) { nextLanes = getHighestPriorityLanes(pingedLanes); nextLanePriority = return_highestLanePriority; } } } } if (nextLanes === NoLanes) { // This should only be reachable if we're suspended // TODO: Consider warning in this path if a fallback timer is not scheduled. return NoLanes; } // If there are higher priority lanes, we'll include them even if they // are suspended. nextLanes = pendingLanes & getEqualOrHigherPriorityLanes(nextLanes); // If we're already in the middle of a render, switching lanes will interrupt // it and we'll lose our progress. We should only do this if the new lanes are // higher priority. if (wipLanes !== NoLanes && wipLanes !== nextLanes && // If we already suspended with a delay, then interrupting is fine. Don't // bother waiting until the root is complete. (wipLanes & suspendedLanes) === NoLanes) { getHighestPriorityLanes(wipLanes); var wipLanePriority = return_highestLanePriority; if (nextLanePriority <= wipLanePriority) { return wipLanes; } else { return_highestLanePriority = nextLanePriority; } } // Check for entangled lanes and add them to the batch. // // A lane is said to be entangled with another when it's not allowed to render // in a batch that does not also include the other lane. Typically we do this // when multiple updates have the same source, and we only want to respond to // the most recent event from that source. // // Note that we apply entanglements *after* checking for partial work above. // This means that if a lane is entangled during an interleaved event while // it's already rendering, we won't interrupt it. This is intentional, since // entanglement is usually "best effort": we'll try our best to render the // lanes in the same batch, but it's not worth throwing out partially // completed work in order to do it. // // For those exceptions where entanglement is semantically important, like // useMutableSource, we should ensure that there is no partial work at the // time we apply the entanglement. var entangledLanes = root.entangledLanes; if (entangledLanes !== NoLanes) { var entanglements = root.entanglements; var lanes = nextLanes & entangledLanes; while (lanes > 0) { var index = pickArbitraryLaneIndex(lanes); var lane = 1 << index; nextLanes |= entanglements[index]; lanes &= ~lane; } } return nextLanes; }
Just Do It