scheduleWork
// 函数调用栈:render > legacyRenderSubtreeIntoContainer > updateContainer > scheduleWork > scheduleUpdateOnFiber
// 传参:scheduleWork(current?1, expirationTime);
// 所以传参 fiber 就是 rootFIber,expirationTime 是当前 fiber 的过期时间
function scheduleUpdateOnFiber(fiber, expirationTime) {
checkForNestedUpdates();
warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber);
var root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);
if (root === null) {
warnAboutUpdateOnUnmountedFiberInDEV(fiber);
return;
}
checkForInterruption(fiber, expirationTime);
recordScheduleUpdate();
// TODO: computeExpirationForFiber also reads the priority. Pass the
// priority as an argument to that function and this one.
var priorityLevel = getCurrentPriorityLevel();
if (expirationTime === Sync) {
if (
// Check if we're inside unbatchedUpdates
// 检查我们是否在unbatchedUpdates内部
(executionContext & LegacyUnbatchedContext) !== NoContext &&
// Check if we're not already rendering
// 检查我们是否尚未渲染
(executionContext & (RenderContext | CommitContext)) === NoContext) {
// Register pending interactions on the root to avoid losing traced interaction data.
// 在根目录上注册待处理的交互,以避免丢失跟踪的交互数据。
schedulePendingInteractions(root, expirationTime);
// This is a legacy edge case.
// The initial mount of a ReactDOM.render-ed root inside of batchedUpdates should be synchronous,
// but layout updates should be deferred until the end of the batch.
// 在 batchedUpdates 内部, ReactDOM.render-ed 生成的 root 节点的初始安装应该是同步的
// 而布局更新应该推迟到批处理结束。
performSyncWorkOnRoot(root);
} else {
ensureRootIsScheduled(root);
schedulePendingInteractions(root, expirationTime);
if (executionContext === NoContext) {
// Flush the synchronous work now, unless we're already working or inside
// a batch. This is intentionally inside scheduleUpdateOnFiber instead of
// scheduleCallbackForFiber to preserve the ability to schedule a callback
// without immediately flushing it. We only do this for user-initiated
// updates, to preserve historical behavior of legacy mode.
flushSyncCallbackQueue();
}
}
} else {
ensureRootIsScheduled(root);
schedulePendingInteractions(root, expirationTime);
}
if ((executionContext & DiscreteEventContext) !== NoContext && ( // Only updates at user-blocking priority or greater are considered
// discrete, even inside a discrete event.
priorityLevel === UserBlockingPriority$2 || priorityLevel === ImmediatePriority)) {
// This is the result of a discrete event. Track the lowest priority
// discrete update per root so we can flush them early, if needed.
if (rootsWithPendingDiscreteUpdates === null) {
rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);
} else {
var lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
if (lastDiscreteTime === undefined || lastDiscreteTime > expirationTime) {
rootsWithPendingDiscreteUpdates.set(root, expirationTime);
}
}
}
}
var scheduleWork = scheduleUpdateOnFiber;
scheduleWork 即 scheduleUpdateOnFiber
源码说明
- 调用 checkForNestedUpdates,检查是否有嵌套更新,如果嵌套超过50层,终止调度;
- 调用 markUpdateTimeFromFiberToRoot,更新 fiber、alternate 的过期时间;沿着 fiber.return 向上遍历父节点,直到找到 rootFiber,在遍历过程中更新父节点及其 alternate 的 childExpirationTime;
- 如果是同步更新
- 当处于 init mount 阶段:
- 调用 schedulePendingInteractions,在 rootFiber 上注册待处理的交互;
- 调用 performSyncWorkOnRoot,生成 workInProgress fiber节点,启动 workLoopSync 循环,深度遍历此 fiber 所有后代子节点,并收集 effect 更新;最后调用 commitRoot 提交更新;
- 当处于 init mount 阶段:
checkForNestedUpdates
// Use these to prevent an infinite loop of nested updates
//使用它们来防止嵌套更新的无限循环
var NESTED_UPDATE_LIMIT = 50;
var nestedUpdateCount = 0;
var rootWithNestedUpdates = null;
var NESTED_PASSIVE_UPDATE_LIMIT = 50;
var nestedPassiveUpdateCount = 0;
// 检查嵌套更新
function checkForNestedUpdates() {
if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
nestedUpdateCount = 0;
rootWithNestedUpdates = null;
{
{
throw Error("Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.");
}
}
}
{
if (nestedPassiveUpdateCount > NESTED_PASSIVE_UPDATE_LIMIT) {
nestedPassiveUpdateCount = 0;
warning$1(false, 'Maximum update depth exceeded. This can happen when a component ' + "calls setState inside useEffect, but useEffect either doesn't " + 'have a dependency array, or one of the dependencies changes on ' + 'every render.');
}
}
}
源码说明
作用:判断是否是无限循环的 update
- 对超过 50 层嵌套的 update,终止进行调度,并报 error:
超过最大更新深度。当组件在 componentWillUpdate 或 componentDidUpdate 内部重复调用 setState 时,可能会发生这种情况。 React 限制了嵌套更新的数量,以防止无限循环。
- 对超过 50 层被动嵌套的 update,终止进行调度,并报 warning:
超过最大更新深度。当一个组件在 useEffect 内调用 setState,但 useEffect 没有依赖项数组,或者其中一个依赖项在每次渲染都会更改时,会发生这种情况。
markUpdateTimeFromFiberToRoot
// 标记从fiber到root的更新时间
function markUpdateTimeFromFiberToRoot(fiber, expirationTime) {
// Update the source fiber's expiration time
// 更新 fiber 的过期时间
if (fiber.expirationTime < expirationTime) {
fiber.expirationTime = expirationTime;
}
var alternate = fiber.alternate;
if (alternate !== null && alternate.expirationTime < expirationTime) {
alternate.expirationTime = expirationTime;
}
// Walk the parent path to the root and update the child expiration time.
// 向上遍历父节点,直到root节点,在遍历的过程中更新子节点的expirationTime
var node = fiber.return; //fiber的父节点
var root = null;
if (node === null && fiber.tag === HostRoot) {
root = fiber.stateNode;
} else {
while (node !== null) {
alternate = node.alternate;
if (node.childExpirationTime < expirationTime) {
node.childExpirationTime = expirationTime;
if (alternate !== null && alternate.childExpirationTime < expirationTime) {
alternate.childExpirationTime = expirationTime;
}
} else if (alternate !== null && alternate.childExpirationTime < expirationTime) {
alternate.childExpirationTime = expirationTime;
}
if (node.return === null && node.tag === HostRoot) {
root = node.stateNode;
break;
}
node = node.return;
}
}
if (root !== null) {
if (workInProgressRoot === root) {
// Received an update to a tree that's in the middle of rendering. Mark
// that's unprocessed work on this root.。
// 当收到对渲染的中间树的更新,标记这是该根目录上的未处理工作。
markUnprocessedUpdateTime(expirationTime);
if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
// The root already suspended with a delay, which means this render
// definitely won't finish. Since we have a new update, let's mark it as
// suspended now, right before marking the incoming update. This has the
// effect of interrupting the current render and switching to the update.
// 当根目录已经被设置为延迟更新,这意味着该渲染一定不会结束。
// 由于我们有新的更新,因此我们在标记新的 update 之前,将其标记为
// 现在暂停。这有中断当前渲染并切换到更新的效果。
// TODO: This happens to work when receiving an update during the render
// phase, because of the trick inside computeExpirationForFiber to
// subtract 1 from `renderExpirationTime` to move it into a
// separate bucket. But we should probably model it with an exception,
// using the same mechanism we use to force hydration of a subtree.
// TODO: This does not account for low pri updates that were already
// scheduled before the root started rendering. Need to track the next
// pending expiration time (perhaps by backtracking the return path) and
// then trigger a restart in the `renderDidSuspendDelayIfPossible` path.
markRootSuspendedAtTime(root, renderExpirationTime);
}
}
// Mark that the root has a pending update.
// 标记根已更新
markRootUpdatedAtTime(root, expirationTime);
}
return root;
}
源码说明
fiber.return 指向 fiber 的父节点
- 更新 fiber 的过期时间,如果 fiber 上存在 alternate,同时更新 alternate 的过期时间;
- 根据 fiber.return 向上遍历父节点,直到找到 rootFiber(node === null && fiber.tag === HostRoot);在遍历的过程中更新父节点的 childExpirationTime,如果父节点上存在 alternate 同时更新 alternate.childExpirationTime;
- 找到 rootFiber 后,根据 rootFiber.stateNode=fiberRoot 的关系,找到 fiberRoot;
- markUnprocessedUpdateTime、markRootSuspendedAtTime 大致上看是用来做中断恢复的处理;
- 调用 markRootUpdatedAtTime 标记 fiberRoot 上有待处理的更新。
markRootUpdatedAtTime
// 标记根已更新
function markRootUpdatedAtTime(root, expirationTime) {
// Update the range of pending times
// 更新待处理时间范围
var firstPendingTime = root.firstPendingTime;
if (expirationTime > firstPendingTime) {
root.firstPendingTime = expirationTime;
}
// Update the range of suspended times. Treat everything lower priority or
// equal to this update as unsuspended.
//更新暂停时间范围。对待所有低优先级的事物或等于此更新为未暂停。
var firstSuspendedTime = root.firstSuspendedTime;
if (firstSuspendedTime !== NoWork) {
if (expirationTime >= firstSuspendedTime) {
// The entire suspended range is now unsuspended.
root.firstSuspendedTime = root.lastSuspendedTime = root.nextKnownPendingLevel = NoWork;
} else if (expirationTime >= root.lastSuspendedTime) {
root.lastSuspendedTime = expirationTime + 1;
}
// This is a pending level. Check if it's higher priority than the next
// known pending level.
// 这是一个待处理的级别。检查其优先级是否高于下一个已知的待处理级别。
if (expirationTime > root.nextKnownPendingLevel) {
root.nextKnownPendingLevel = expirationTime;
}
}
}
源码说明
- 更新 fiberRoot 的待处理的时间范围,即 firstPendingTime;
- 并更新 fiberRoot 相关暂停的时间范围,即 firstSuspendedTime、lastSuspendedTime;
- 跟下一个已知待处理的级别比较,如果当前 expirationTime 级别更高,更新 nextKnownPendingLevel;
markUnprocessedUpdateTime
// 标记未处理的更新时间
function markUnprocessedUpdateTime(expirationTime) {
if (expirationTime > workInProgressRootNextUnprocessedUpdateTime) {
workInProgressRootNextUnprocessedUpdateTime = expirationTime;
}
}
// ??
markRootSuspendedAtTime
function markRootSuspendedAtTime(root, expirationTime) {
var firstSuspendedTime = root.firstSuspendedTime;
var lastSuspendedTime = root.lastSuspendedTime;
if (firstSuspendedTime < expirationTime) {
root.firstSuspendedTime = expirationTime;
}
if (lastSuspendedTime > expirationTime || firstSuspendedTime === NoWork) {
root.lastSuspendedTime = expirationTime;
}
if (expirationTime <= root.lastPingedTime) {
root.lastPingedTime = NoWork;
}
if (expirationTime <= root.lastExpiredTime) {
root.lastExpiredTime = NoWork;
}
}
// ??
checkForInterruption
// The root we're working on
var workInProgressRoot = null;
var enableUserTimingAPI = true;
var interruptedBy = null;
function checkForInterruption(fiberThatReceivedUpdate, updateExpirationTime) {
if (enableUserTimingAPI && workInProgressRoot !== null && updateExpirationTime > renderExpirationTime) {
interruptedBy = fiberThatReceivedUpdate;
}
}
源码说明
判断是否有高优先级任务打断当前正在执行的任务
- 如果当前 fiber 的优先级更高,需要打断当前执行的任务,立即执行该 fiber 上的 update,更新 interruptedBy 全局属性;
- enableUserTimingAPI 的作用??
recordScheduleUpdate
// If we're in the middle of user code, which fiber and method is it?
// Reusing `currentFiber` would be confusing for this because user code fiber
// can change during commit phase too, but we don't need to unwind it (since
// lifecycles in the commit phase don't resemble a tree).
var currentPhase = null;
var currentPhaseFiber = null;
// Did lifecycle hook schedule an update? This is often a performance problem,
// so we will keep track of it, and include it in the report.
// Track commits caused by cascading updates.
var isCommitting = false;
var hasScheduledUpdateInCurrentCommit = false;
var hasScheduledUpdateInCurrentPhase = false;
function recordScheduleUpdate() {
if (enableUserTimingAPI) {
if (isCommitting) {
hasScheduledUpdateInCurrentCommit = true;
}
if (currentPhase !== null && currentPhase !== 'componentWillMount' && currentPhase !== 'componentWillReceiveProps') {
hasScheduledUpdateInCurrentPhase = true;
}
}
}
源码说明
用来记录调度器的执行状态
schedulePendingInteractions
var enableSchedulerTracing = true; // SSR experiments
var tracing = require('scheduler/tracing');
function schedulePendingInteractions(root, expirationTime) {
// This is called when work is scheduled on a root.
// It associates the current interactions with the newly-scheduled expiration.
// They will be restored when that expiration is later committed.
// 当工作安排在根上时调用。
// 将当前交互与新计划的过期时间相关联。
// 它们将在以后到期时恢复。
if (!enableSchedulerTracing) {
return;
}
scheduleInteractions(root, expirationTime, tracing.__interactionsRef.current);
}
源码说明
安排待处理的交互
- 只是 scheduleInteractions 的加壳函数
scheduleInteractions
var DEFAULT_THREAD_ID = 0;
// Counters used to generate unique IDs.
var interactionIDCounter = 0;
var threadIDCounter = 0;
function scheduleInteractions(root, expirationTime, interactions) {
if (!enableSchedulerTracing) {
return;
}
if (interactions.size > 0) {
var pendingInteractionMap = root.pendingInteractionMap;
var pendingInteractions = pendingInteractionMap.get(expirationTime);
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(expirationTime, new Set(interactions));
// Update the pending async work count for the current interactions.
// 更新当前交互的待处理异步工作计数。
interactions.forEach(function (interaction) {
interaction.__count++;
});
}
var subscriber = tracing.__subscriberRef.current;
if (subscriber !== null) {
var threadID = computeThreadID(root, expirationTime);
subscriber.onWorkScheduled(interactions, threadID);
}
}
}
源码说明
主要涉及到 scheduler-tracing 交互的处理,后面再详解。
computeThreadID
function computeThreadID(root, expirationTime) {
// Interaction threads are unique per root and expiration time.
return expirationTime * 1000 + root.interactionThreadID;
}
performSyncWorkOnRoot
// 函数调用栈:render > legacyRenderSubtreeIntoContainer > updateContainer >
// > scheduleWork > scheduleUpdateOnFiber > performSyncWorkOnRoot
// 传参:这里的 root 就是 markUpdateTimeFromFiberToRoot 返回的 fiberRoot
function performSyncWorkOnRoot(root) {
// Check if there's expired work on this root. Otherwise, render at Sync.
//检查此根目录上是否有过期的工作。否则,在同步时渲染。
var lastExpiredTime = root.lastExpiredTime;
var expirationTime = lastExpiredTime !== NoWork ? lastExpiredTime : Sync;
if (root.finishedExpirationTime === expirationTime) {
// There's already a pending commit at this expiration time.
// TODO: This is poorly factored. This case only exists for the
// batch.commit() API.
commitRoot(root);
} else {
if (!((executionContext & (RenderContext | CommitContext)) === NoContext)) {
{
throw Error("Should not already be working.");
}
}
flushPassiveEffects();
// If the root or expiration time have changed, throw out the existing stack
// and prepare a fresh one. Otherwise we'll continue where we left off.
// 如果根或到期时间已更改,则丢弃现有堆栈并准备一个新的。
// 否则,我们将从中断的地方继续。
if (root !== workInProgressRoot || expirationTime !== renderExpirationTime) {
prepareFreshStack(root, expirationTime);
startWorkOnPendingInteractions(root, expirationTime);
}
// If we have a work-in-progress fiber, it means there's still work to do
// in this root.
if (workInProgress !== null) {
var prevExecutionContext = executionContext;
executionContext |= RenderContext;
var prevDispatcher = pushDispatcher(root);
var prevInteractions = pushInteractions(root);
startWorkLoopTimer(workInProgress);
do {
try {
workLoopSync();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
resetContextDependencies();
executionContext = prevExecutionContext;
popDispatcher(prevDispatcher);
if (enableSchedulerTracing) {
popInteractions(prevInteractions);
}
if (workInProgressRootExitStatus === RootFatalErrored) {
var fatalError = workInProgressRootFatalError;
stopInterruptedWorkLoopTimer();
prepareFreshStack(root, expirationTime);
markRootSuspendedAtTime(root, expirationTime);
ensureRootIsScheduled(root);
throw fatalError;
}
if (workInProgress !== null) {
// This is a sync render, so we should have finished the whole tree.
{
{
throw Error("Cannot commit an incomplete root. This error is likely caused by a bug in React. Please file an issue.");
}
}
} else {
// We now have a consistent tree. Because this is a sync render, we
// will commit it even if something suspended.
stopFinishedWorkLoopTimer();
root.finishedWork = root.current.alternate;
root.finishedExpirationTime = expirationTime;
finishSyncRender(root, workInProgressRootExitStatus, expirationTime);
}
// Before exiting, make sure there's a callback scheduled for the next
// pending level.
ensureRootIsScheduled(root);
}
}
return null;
}
prepareFreshStack
// 准备新的堆栈
function prepareFreshStack(root, expirationTime) {
root.finishedWork = null;
root.finishedExpirationTime = NoWork;
var timeoutHandle = root.timeoutHandle;
if (timeoutHandle !== noTimeout) {
// The root previous suspended and scheduled a timeout to commit a fallback
// state. Now that we have additional work, cancel the timeout.
root.timeoutHandle = noTimeout;
// $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
cancelTimeout(timeoutHandle);
}
if (workInProgress !== null) {
var interruptedWork = workInProgress.return;
while (interruptedWork !== null) {
unwindInterruptedWork(interruptedWork);
interruptedWork = interruptedWork.return;
}
}
workInProgressRoot = root;
workInProgress = createWorkInProgress(root.current, null, expirationTime);
renderExpirationTime = expirationTime;
workInProgressRootExitStatus = RootIncomplete;
workInProgressRootFatalError = null;
workInProgressRootLatestProcessedExpirationTime = Sync;
workInProgressRootLatestSuspenseTimeout = Sync;
workInProgressRootCanSuspendUsingConfig = null;
workInProgressRootNextUnprocessedUpdateTime = NoWork;
workInProgressRootHasPendingPing = false;
if (enableSchedulerTracing) {
spawnedWorkDuringRender = null;
}
{
ReactStrictModeWarnings.discardPendingWarnings();
componentsThatTriggeredHighPriSuspend = null;
}
}
源码说明
- 设置 workInProgress 相关的各个属性,关键是 workInProgressRoot、workInProgress(调用 createWorkInProgress 生成);
createWorkInProgress
// This is used to create an alternate fiber to do work on.
// 用于创建要处理的备用 fiber
// 这里的 current 是 rootFiber
function createWorkInProgress(current, pendingProps, expirationTime) {
var workInProgress = current.alternate;
if (workInProgress === null) {
// We use a double buffering pooling technique because we know that we'll
// only ever need at most two versions of a tree. We pool the "other" unused
// node that we're free to reuse. This is lazily created to avoid allocating
// extra objects for things that are never updated. It also allow us to
// reclaim the extra memory if needed.
//我们使用双重缓冲池技术,因为我们知道最多只需要一棵树的两个版本。
//我们将“其他”未使用的节点合并,这样我们就可以自由重用。
//为了避免为永不更新的对象的对象分配额外的内存,它是懒创建的。
//这也使我们能够在需要时,回收额外的内存。
workInProgress = createFiber(current.tag, pendingProps, current.key, current.mode);
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
{
// DEV-only fields
workInProgress._debugID = current._debugID;
workInProgress._debugSource = current._debugSource;
workInProgress._debugOwner = current._debugOwner;
workInProgress._debugHookTypes = current._debugHookTypes;
}
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
workInProgress.pendingProps = pendingProps;
// We already have an alternate.
// Reset the effect tag.
workInProgress.effectTag = NoEffect;
// The effect list is no longer valid.
workInProgress.nextEffect = null;
workInProgress.firstEffect = null;
workInProgress.lastEffect = null;
if (enableProfilerTimer) {
// We intentionally reset, rather than copy, actualDuration & actualStartTime.
// This prevents time from endlessly accumulating in new commits.
// This has the downside of resetting values for different priority renders,
// But works for yielding (the common case) and should support resuming.
workInProgress.actualDuration = 0;
workInProgress.actualStartTime = -1;
}
}
workInProgress.childExpirationTime = current.childExpirationTime;
workInProgress.expirationTime = current.expirationTime;
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;
// Clone the dependencies object. This is mutated during the render phase,
// so it cannot be shared with the current fiber.
var currentDependencies = current.dependencies;
workInProgress.dependencies = currentDependencies === null ? null : {
expirationTime: currentDependencies.expirationTime,
firstContext: currentDependencies.firstContext,
responders: currentDependencies.responders
};
// These will be overridden during the parent's reconciliation
workInProgress.sibling = current.sibling;
workInProgress.index = current.index;
workInProgress.ref = current.ref;
if (enableProfilerTimer) {
workInProgress.selfBaseDuration = current.selfBaseDuration;
workInProgress.treeBaseDuration = current.treeBaseDuration;
}
{
workInProgress._debugNeedsRemount = current._debugNeedsRemount;
switch (workInProgress.tag) {
case IndeterminateComponent:
case FunctionComponent:
case SimpleMemoComponent:
workInProgress.type = resolveFunctionForHotReloading(current.type);
break;
case ClassComponent:
workInProgress.type = resolveClassForHotReloading(current.type);
break;
case ForwardRef:
workInProgress.type = resolveForwardRefForHotReloading(current.type);
break;
default:
break;
}
}
return workInProgress;
}
源码说明
- 如果 workInProgress 不存在,根据传入的 fiber 复制一份,并将各自的 alternate 属性指向对方;
- 如果已存在,重置 effectTag 和相关的指针(nextEffect、firstEffect、lastEffect);
- 避免 dependencies 属性的引用赋值,其他属性直接赋值;
startWorkOnPendingInteractions
// 开始处理待处理的交互
function startWorkOnPendingInteractions(root, expirationTime) {
// This is called when new work is started on a root.
if (!enableSchedulerTracing) {
return;
}
// Determine which interactions this batch of work currently includes, So that
// we can accurately attribute time spent working on it, And so that cascading
// work triggered during the render phase will be associated with it.
var interactions = new Set();
root.pendingInteractionMap.forEach(function (scheduledInteractions, scheduledExpirationTime) {
if (scheduledExpirationTime >= expirationTime) {
scheduledInteractions.forEach(function (interaction) {
return interactions.add(interaction);
});
}
});
// Store the current set of interactions on the FiberRoot for a few reasons:
// We can re-use it in hot functions like performConcurrentWorkOnRoot()
// without having to recalculate it. We will also use it in commitWork() to
// pass to any Profiler onRender() hooks. This also provides DevTools with a
// way to access it when the onCommitRoot() hook is called.
root.memoizedInteractions = interactions;
if (interactions.size > 0) {
var subscriber = tracing.__subscriberRef.current;
if (subscriber !== null) {
var threadID = computeThreadID(root, expirationTime);
try {
subscriber.onWorkStarted(interactions, threadID);
} catch (error) {
// If the subscriber throws, rethrow it in a separate task
scheduleCallback(ImmediatePriority, function () {
throw error;
});
}
}
}
}
源码说明
涉及 react 交互逻辑处理,后边再详解。
workLoopSync
function workLoopSync() {
// Already timed out, so perform work without checking if we need to yield.
while (workInProgress !== null) {
workInProgress = performUnitOfWork(workInProgress);
}
}
源码说明
循环调用 performUnitOfWork 处理 fiber 节点。
performUnitOfWork
function performUnitOfWork(unitOfWork) {
// The current, flushed, state of this fiber is the alternate. Ideally
// nothing should rely on this, but relying on it here means that we don't
// need an additional field on the work in progress.
// 该fiber的当前已刷新状态是作为备用。
// 理想情况不应该依赖于此,但是在这里依赖它意味着我们
// 在工作进行中的不需要再一个附加字段。
var current?1 = unitOfWork.alternate;
startWorkTimer(unitOfWork);
setCurrentFiber(unitOfWork);
var next;
if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
startProfilerTimer(unitOfWork);
next = beginWork?1(current?1, unitOfWork, renderExpirationTime);
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
} else {
next = beginWork?1(current?1, unitOfWork, renderExpirationTime);
}
resetCurrentFiber();
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// If this doesn't spawn new work, complete the current work.
next = completeUnitOfWork(unitOfWork);
}
ReactCurrentOwner$2.current = null;
return next;
}
源码说明
- 处理 fiber 单元,核心就两个函数:beginWork?1、completeUnitOfWork,只为了拿到下一个需要处理的 fiber 节点。
这两个函数是调和阶段的核心,放到下一节详解。
flushSyncCallbackQueue
// Intentionally not named imports because Rollup would use dynamic dispatch for
// CommonJS interop named imports.
var Scheduler_runWithPriority = Scheduler.unstable_runWithPriority;
var Scheduler_scheduleCallback = Scheduler.unstable_scheduleCallback;
var Scheduler_cancelCallback = Scheduler.unstable_cancelCallback;
var Scheduler_shouldYield = Scheduler.unstable_shouldYield;
var Scheduler_requestPaint = Scheduler.unstable_requestPaint;
var Scheduler_now = Scheduler.unstable_now;
var Scheduler_getCurrentPriorityLevel = Scheduler.unstable_getCurrentPriorityLevel;
var Scheduler_ImmediatePriority = Scheduler.unstable_ImmediatePriority;
var Scheduler_UserBlockingPriority = Scheduler.unstable_UserBlockingPriority;
var Scheduler_NormalPriority = Scheduler.unstable_NormalPriority;
var Scheduler_LowPriority = Scheduler.unstable_LowPriority;
var Scheduler_IdlePriority = Scheduler.unstable_IdlePriority;
var immediateQueueCallbackNode = null;
function flushSyncCallbackQueue() {
if (immediateQueueCallbackNode !== null) {
var node = immediateQueueCallbackNode;
immediateQueueCallbackNode = null;
Scheduler_cancelCallback(node);
}
flushSyncCallbackQueueImpl();
}
flushSyncCallbackQueueImpl
//刷新同步回调队列实现
function flushSyncCallbackQueueImpl() {
if (!isFlushingSyncQueue && syncQueue !== null) {
// Prevent re-entrancy.
isFlushingSyncQueue = true;
var i = 0;
try {
var _isSync = true;
var queue = syncQueue;
runWithPriority$2(ImmediatePriority, function () {
for (; i < queue.length; i++) {
var callback = queue[i];
do {
callback = callback(_isSync);
} while (callback !== null);
}
});
syncQueue = null;
} catch (error) {
// If something throws, leave the remaining callbacks on the queue.
if (syncQueue !== null) {
syncQueue = syncQueue.slice(i + 1);
} // Resume flushing in the next tick
Scheduler_scheduleCallback(Scheduler_ImmediatePriority, flushSyncCallbackQueue);
throw error;
} finally {
isFlushingSyncQueue = false;
}
}
}