上一篇走到了updateContainer
updateContainer
updateContainer 就是根据过期时间来进行更新
function updateContainer(element, container, parentComponent, callback) {
// 创建的hostroot对象
var current$$1 = container.current;
// 获得当前的时间,相同的时间虽然触发的顺序不同,但是时间设置为一样
var currentTime = requestCurrentTime();
// 计算fiber到期时间, 这里是同步渲染,expirationTime 设置为最大值
var expirationTime = computeExpirationForFiber(currentTime, current$$1);
// 这里element是app组件,container是root对象,包含hostRoot, parentComponent null, expirationTime 最大值也就是同步
return updateContainerAtExpirationTime(element, container, parentComponent, expirationTime, callback);
}
updateContainerAtExpirationTime
updateContainerAtExpirationTime 函数名就很明显了,在到期时间内更新
function updateContainerAtExpirationTime(element, container, parentComponent, expirationTime, callback) {
// TODO: If this is a nested container, this won't be the root.
// HostRoot对象
var current?1 = container.current;
{
// ReactFiberInstrumentation_1可以debug fiber
if (ReactFiberInstrumentation_1.debugTool) {
if (current?1.alternate === null) {
ReactFiberInstrumentation_1.debugTool.onMountContainer(container);
} else if (element === null) {
ReactFiberInstrumentation_1.debugTool.onUnmountContainer(container);
} else {
ReactFiberInstrumentation_1.debugTool.onUpdateContainer(container);
}
}
}
// 初始化返回空对象
var context = getContextForSubtree(parentComponent);
if (container.context === null) {
container.context = context;
} else {
container.pendingContext = context;
}
// 开始root的更新
return scheduleRootUpdate(current?1, element, expirationTime, callback);
}
scheduleRootUpdate
scheduleRootUpdate 准备root更新,通过scheduleWork开始准备安排工作
// current$$1 HostRoot对象,element app 组件, expirationTime 到期时间,callback回调函数
function scheduleRootUpdate(current$$1, element, expirationTime, callback) {
{
//phase 阶段
if (phase === 'render' && current !== null && !didWarnAboutNestedUpdates) {
didWarnAboutNestedUpdates = true;
warningWithoutStack$1(false, 'Render methods should be a pure function of props and state; ' + 'triggering nested component updates from render is not allowed. ' + 'If necessary, trigger nested updates in componentDidUpdate.\n\n' + 'Check the render method of %s.', getComponentName(current.type) || 'Unknown');
}
}
// 一个简单的js 对象
var update = createUpdate(expirationTime);
// Caution 警告
// Caution: React DevTools currently depends on this property
// being called "element".
update.payload = { element: element };
callback = callback === undefined ? null : callback;
if (callback !== null) {
!(typeof callback === 'function') ? warningWithoutStack$1(false, 'render(...): Expected the last optional `callback` argument to be a ' + 'function. Instead received: %s.', callback) : void 0;
update.callback = callback;
}
// 暂时不起作用, 刷新effects
flushPassiveEffects();
// 形成更新队列
enqueueUpdate(current$$1, update);
// 安排工作
scheduleWork(current$$1, expirationTime);
return expirationTime;
}
scheduleWork
scheduleWork 就是转了一下,转到了requestWork
function scheduleWork(fiber, expirationTime) {
// fiber node, 通过fiber的statenode属性回到创建的root,
var root = scheduleWorkToRoot(fiber, expirationTime);
if (root === null) {
{
switch (fiber.tag) {
case ClassComponent:
warnAboutUpdateOnUnmounted(fiber, true);
break;
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent:
warnAboutUpdateOnUnmounted(fiber, false);
break;
}
}
return;
}
if (!isWorking && nextRenderExpirationTime !== NoWork && expirationTime > nextRenderExpirationTime) {
// This is an interruption. (Used for performance tracking.)
interruptedBy = fiber;
resetStack();
}
markPendingPriorityLevel(root, expirationTime);
if (
// If we're in the render phase, we don't need to schedule this root
// for an update, because we'll do it before we exit...
!isWorking || isCommitting$1 ||
// ...unless this is a different root than the one we're rendering.
nextRoot !== root) {
var rootExpirationTime = root.expirationTime;
requestWork(root, rootExpirationTime);
}
if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
// Reset this back to zero so subsequent updates don't throw.
nestedUpdateCount = 0;
invariant(false, '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.');
}
}
requestWork
requestWork 请求工作,这里把root插入到schdule链表里边去,然后开始performSyncWork
function requestWork(root, expirationTime) {
// 插入到schedule链表里
addRootToSchedule(root, expirationTime);
if (isRendering) {
// Prevent reentrancy. Remaining work will be scheduled at the end of
// the currently rendering batch.
return;
}
if (isBatchingUpdates) {
// Flush work at the end of the batch.
if (isUnbatchingUpdates) {
// ...unless we're inside unbatchedUpdates, in which case we should
// flush it now.
nextFlushedRoot = root;
nextFlushedExpirationTime = Sync;
performWorkOnRoot(root, Sync, false);
}
return;
}
// TODO: Get rid of Sync and use current time?
if (expirationTime === Sync) {
performSyncWork();
} else {
scheduleCallbackWithExpirationTime(root, expirationTime);
}
}
performSyncWork
function performSyncWork() {
performWork(Sync, false);
}
performWork
基本上没做其他处理,然后到了performWorkOnRoot, 最后 finishRendering
function performWork(minExpirationTime, isYieldy) {
// Keep working on roots until there's no more work, or until there's a higher
// priority event.
// 有没优先级更高的
findHighestPriorityRoot();
if (isYieldy) {
recomputeCurrentRendererTime();
currentSchedulerTime = currentRendererTime;
if (enableUserTimingAPI) {
var didExpire = nextFlushedExpirationTime > currentRendererTime;
var timeout = expirationTimeToMs(nextFlushedExpirationTime);
stopRequestCallbackTimer(didExpire, timeout);
}
while (nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork && minExpirationTime <= nextFlushedExpirationTime && !(didYield && currentRendererTime > nextFlushedExpirationTime)) {
performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, currentRendererTime > nextFlushedExpirationTime);
findHighestPriorityRoot();
recomputeCurrentRendererTime();
currentSchedulerTime = currentRendererTime;
}
} else {
while (nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork && minExpirationTime <= nextFlushedExpirationTime) {
performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, false);
findHighestPriorityRoot();
}
}
// We're done flushing work. Either we ran out of time in this callback,
// or there's no more work left with sufficient priority.
// If we're inside a callback, set this to false since we just completed it.
if (isYieldy) {
callbackExpirationTime = NoWork;
callbackID = null;
}
// If there's work left over, schedule a new callback.
if (nextFlushedExpirationTime !== NoWork) {
scheduleCallbackWithExpirationTime(nextFlushedRoot, nextFlushedExpirationTime);
}
// Clean-up.
finishRendering();
}
performWorkOnRoot
performWorkOnRoot 开始render root,首先看下是否isYieldy,然后根据finishedWork时候存在,判断是否完成complete. 这里首先renderRoot,然后会completeRoot
function performWorkOnRoot(root, expirationTime, isYieldy) {
!!isRendering ? invariant(false, 'performWorkOnRoot was called recursively. This error is likely caused by a bug in React. Please file an issue.') : void 0;
isRendering = true;
// Check if this is async work or sync/expired work.
// isisYieldy async
if (!isYieldy) {
// Flush work without yielding.
// TODO: Non-yieldy work does not necessarily imply expired work. A renderer
// may want to perform some work without yielding, but also without
// requiring the root to complete (by triggering placeholders).
var finishedWork = root.finishedWork;
if (finishedWork !== null) {
// This root is already complete. We can commit it.
completeRoot(root, finishedWork, expirationTime);
} else {
root.finishedWork = null;
// If this root previously suspended, clear its existing timeout, since
// we're about to try rendering again.
var timeoutHandle = root.timeoutHandle;
if (timeoutHandle !== noTimeout) {
root.timeoutHandle = noTimeout;
// $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
cancelTimeout(timeoutHandle);
}
renderRoot(root, isYieldy);
finishedWork = root.finishedWork;
// 结束
if (finishedWork !== null) {
// We've completed the root. Commit it.
completeRoot(root, finishedWork, expirationTime);
}
}
} else {
// Flush async work.
var _finishedWork = root.finishedWork;
if (_finishedWork !== null) {
// This root is already complete. We can commit it.
completeRoot(root, _finishedWork, expirationTime);
} else {
root.finishedWork = null;
// If this root previously suspended, clear its existing timeout, since
// we're about to try rendering again.
var _timeoutHandle = root.timeoutHandle;
if (_timeoutHandle !== noTimeout) {
root.timeoutHandle = noTimeout;
// $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
cancelTimeout(_timeoutHandle);
}
renderRoot(root, isYieldy);
_finishedWork = root.finishedWork;
if (_finishedWork !== null) {
// We've completed the root. Check the if we should yield one more time
// before committing.
if (!shouldYieldToRenderer()) {
// Still time left. Commit the root.
completeRoot(root, _finishedWork, expirationTime);
} else {
// There's no time left. Mark this root as complete. We'll come
// back and commit it later.
root.finishedWork = _finishedWork;
}
}
}
}
isRendering = false;
}
renderRoot
renderRoot 建立起work-in-progress 树,通过workloop建立,然后onCompleted准备commit
function renderRoot(root, isYieldy) {
!!isWorking ? invariant(false, 'renderRoot was called recursively. This error is likely caused by a bug in React. Please file an issue.') : void 0;
flushPassiveEffects();
isWorking = true;
if (enableHooks) {
ReactCurrentOwner$2.currentDispatcher = Dispatcher;
} else {
ReactCurrentOwner$2.currentDispatcher = DispatcherWithoutHooks;
}
var expirationTime = root.nextExpirationTimeToWorkOn;
// Check if we're starting from a fresh stack, or if we're resuming from
// previously yielded work.
if (expirationTime !== nextRenderExpirationTime || root !== nextRoot || nextUnitOfWork === null) {
// Reset the stack and start working from the root.
resetStack();
nextRoot = root;
nextRenderExpirationTime = expirationTime;
// 创建work-in-progress树
nextUnitOfWork = createWorkInProgress(nextRoot.current, null, nextRenderExpirationTime);
root.pendingCommitExpirationTime = NoWork;
if (enableSchedulerTracing) {
// Determine which interactions this batch of work currently includes,
// So that we can accurately attribute time spent working on 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 renderRoot() 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(expirationTime, root.interactionThreadID);
try {
subscriber.onWorkStarted(interactions, threadID);
} catch (error) {
// Work thrown by an interaction tracing subscriber should be rethrown,
// But only once it's safe (to avoid leaveing the scheduler in an invalid state).
// Store the error for now and we'll re-throw in finishRendering().
if (!hasUnhandledError) {
hasUnhandledError = true;
unhandledError = error;
}
}
}
}
}
}
var prevInteractions = null;
if (enableSchedulerTracing) {
// We're about to start new traced work.
// Restore pending interactions so cascading work triggered during the render phase will be accounted for.
prevInteractions = tracing.__interactionsRef.current;
tracing.__interactionsRef.current = root.memoizedInteractions;
}
var didFatal = false;
startWorkLoopTimer(nextUnitOfWork);
do {
try {
workLoop(isYieldy);
} catch (thrownValue) {
resetContextDependences();
resetHooks();
// Reset in case completion throws.
// This is only used in DEV and when replaying is on.
var mayReplay = void 0;
if (true && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
mayReplay = mayReplayFailedUnitOfWork;
mayReplayFailedUnitOfWork = true;
}
if (nextUnitOfWork === null) {
// This is a fatal error.
didFatal = true;
onUncaughtError(thrownValue);
} else {
if (enableProfilerTimer && nextUnitOfWork.mode & ProfileMode) {
// Record the time spent rendering before an error was thrown.
// This avoids inaccurate Profiler durations in the case of a suspended render.
stopProfilerTimerIfRunningAndRecordDelta(nextUnitOfWork, true);
}
{
// Reset global debug state
// We assume this is defined in DEV
resetCurrentlyProcessingQueue();
}
if (true && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
if (mayReplay) {
var failedUnitOfWork = nextUnitOfWork;
replayUnitOfWork(failedUnitOfWork, thrownValue, isYieldy);
}
}
// TODO: we already know this isn't true in some cases.
// At least this shows a nicer error message until we figure out the cause.
// https://github.com/facebook/react/issues/12449#issuecomment-386727431
!(nextUnitOfWork !== null) ? invariant(false, 'Failed to replay rendering after an error. This is likely caused by a bug in React. Please file an issue with a reproducing case to help us find it.') : void 0;
var sourceFiber = nextUnitOfWork;
var returnFiber = sourceFiber.return;
if (returnFiber === null) {
// This is the root. The root could capture its own errors. However,
// we don't know if it errors before or after we pushed the host
// context. This information is needed to avoid a stack mismatch.
// Because we're not sure, treat this as a fatal error. We could track
// which phase it fails in, but doesn't seem worth it. At least
// for now.
didFatal = true;
onUncaughtError(thrownValue);
} else {
throwException(root, returnFiber, sourceFiber, thrownValue, nextRenderExpirationTime);
nextUnitOfWork = completeUnitOfWork(sourceFiber);
continue;
}
}
}
break;
} while (true);
if (enableSchedulerTracing) {
// Traced work is done for now; restore the previous interactions.
tracing.__interactionsRef.current = prevInteractions;
}
// We're done performing work. Time to clean up.
isWorking = false;
ReactCurrentOwner$2.currentDispatcher = null;
resetContextDependences();
resetHooks();
// Yield back to main thread.
if (didFatal) {
var _didCompleteRoot = false;
stopWorkLoopTimer(interruptedBy, _didCompleteRoot);
interruptedBy = null;
// There was a fatal error.
{
resetStackAfterFatalErrorInDev();
}
// `nextRoot` points to the in-progress root. A non-null value indicates
// that we're in the middle of an async render. Set it to null to indicate
// there's no more work to be done in the current batch.
nextRoot = null;
onFatal(root);
return;
}
if (nextUnitOfWork !== null) {
// There's still remaining async work in this tree, but we ran out of time
// in the current frame. Yield back to the renderer. Unless we're
// interrupted by a higher priority update, we'll continue later from where
// we left off.
var _didCompleteRoot2 = false;
stopWorkLoopTimer(interruptedBy, _didCompleteRoot2);
interruptedBy = null;
onYield(root);
return;
}
// We completed the whole tree.
var didCompleteRoot = true;
stopWorkLoopTimer(interruptedBy, didCompleteRoot);
var rootWorkInProgress = root.current.alternate;
!(rootWorkInProgress !== null) ? invariant(false, 'Finished root should have a work-in-progress. This error is likely caused by a bug in React. Please file an issue.') : void 0;
// `nextRoot` points to the in-progress root. A non-null value indicates
// that we're in the middle of an async render. Set it to null to indicate
// there's no more work to be done in the current batch.
nextRoot = null;
interruptedBy = null;
if (nextRenderDidError) {
// There was an error
if (hasLowerPriorityWork(root, expirationTime)) {
// There's lower priority work. If so, it may have the effect of fixing
// the exception that was just thrown. Exit without committing. This is
// similar to a suspend, but without a timeout because we're not waiting
// for a promise to resolve. React will restart at the lower
// priority level.
markSuspendedPriorityLevel(root, expirationTime);
var suspendedExpirationTime = expirationTime;
var rootExpirationTime = root.expirationTime;
onSuspend(root, rootWorkInProgress, suspendedExpirationTime, rootExpirationTime, -1 // Indicates no timeout
);
return;
} else if (
// There's no lower priority work, but we're rendering asynchronously.
// Synchronsouly attempt to render the same level one more time. This is
// similar to a suspend, but without a timeout because we're not waiting
// for a promise to resolve.
!root.didError && isYieldy) {
root.didError = true;
var _suspendedExpirationTime = root.nextExpirationTimeToWorkOn = expirationTime;
var _rootExpirationTime = root.expirationTime = Sync;
onSuspend(root, rootWorkInProgress, _suspendedExpirationTime, _rootExpirationTime, -1 // Indicates no timeout
);
return;
}
}
if (isYieldy && nextLatestAbsoluteTimeoutMs !== -1) {
// The tree was suspended.
var _suspendedExpirationTime2 = expirationTime;
markSuspendedPriorityLevel(root, _suspendedExpirationTime2);
// Find the earliest uncommitted expiration time in the tree, including
// work that is suspended. The timeout threshold cannot be longer than
// the overall expiration.
var earliestExpirationTime = findEarliestOutstandingPriorityLevel(root, expirationTime);
var earliestExpirationTimeMs = expirationTimeToMs(earliestExpirationTime);
if (earliestExpirationTimeMs < nextLatestAbsoluteTimeoutMs) {
nextLatestAbsoluteTimeoutMs = earliestExpirationTimeMs;
}
// Subtract the current time from the absolute timeout to get the number
// of milliseconds until the timeout. In other words, convert an absolute
// timestamp to a relative time. This is the value that is passed
// to `setTimeout`.
var currentTimeMs = expirationTimeToMs(requestCurrentTime());
var msUntilTimeout = nextLatestAbsoluteTimeoutMs - currentTimeMs;
msUntilTimeout = msUntilTimeout < 0 ? 0 : msUntilTimeout;
// TODO: Account for the Just Noticeable Difference
var _rootExpirationTime2 = root.expirationTime;
onSuspend(root, rootWorkInProgress, _suspendedExpirationTime2, _rootExpirationTime2, msUntilTimeout);
return;
}
// Ready to commit.
onComplete(root, rootWorkInProgress, expirationTime);
}
workLoop
workLoop是一个循环,不断的检测是否存在nextUnitOfWork
function workLoop(isYieldy) {
if (!isYieldy) {
// Flush work without yielding
while (nextUnitOfWork !== null) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
} else {
// Flush asynchronous work until there's a higher priority event
while (nextUnitOfWork !== null && !shouldYieldToRenderer()) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
}
}
暂时先到这performUnitOfWork