1 )概述
performUnitOfWork
以及 completeUnitOfWork
beginWork
, 在这个过程中2 )更新过程回顾
一开始
root
|
| current
↓
RootFiber -----alternate-----> workInProgress
| |
| |
↓ ↓
childFiber childWIP (childFiber 的 workInProgress)
更新完成之后
root
\
\
\
\
\
\
\
\
\
\
\
\ current
\
\
\
\
\
\
\
\
\
\
\
\
↘
RootFiber -----alternate-----> workInProgress
| |
| |
↓ ↓
childFiber childWIP (childFiber 的 workInProgress)
root.current = finishedWork
(packages/react-reconciler/src/ReactFiberScheduler.js#L730)3 )详解 Suspend
renderRoot
, 在 workLoop
执行完成之后,会执行一堆判断定位到 packages/react-reconciler/src/ReactFiberScheduler.js#L1381
// Yield back to main thread.
if (didFatal) {
const didCompleteRoot = false;
stopWorkLoopTimer(interruptedBy, didCompleteRoot);
interruptedBy = null;
// There was a fatal error.
if (__DEV__) {
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.
const didCompleteRoot = false;
stopWorkLoopTimer(interruptedBy, didCompleteRoot);
interruptedBy = null;
onYield(root);
return;
}
// We completed the whole tree.
const didCompleteRoot = true;
stopWorkLoopTimer(interruptedBy, didCompleteRoot);
const rootWorkInProgress = root.current.alternate;
invariant(
rootWorkInProgress !== null,
'Finished root should have a work-in-progress. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
// `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);
const suspendedExpirationTime = expirationTime;
const 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;
const suspendedExpirationTime = (root.nextExpirationTimeToWorkOn = expirationTime);
const rootExpirationTime = (root.expirationTime = Sync);
onSuspend(
root,
rootWorkInProgress,
suspendedExpirationTime,
rootExpirationTime,
-1, // Indicates no timeout
);
return;
}
}
if (isYieldy && nextLatestAbsoluteTimeoutMs !== -1) {
// The tree was suspended.
const suspendedExpirationTime = expirationTime;
markSuspendedPriorityLevel(root, suspendedExpirationTime);
// Find the earliest uncommitted expiration time in the tree, including
// work that is suspended. The timeout threshold cannot be longer than
// the overall expiration.
const earliestExpirationTime = findEarliestOutstandingPriorityLevel(
root,
expirationTime,
);
const 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`.
const currentTimeMs = expirationTimeToMs(requestCurrentTime());
let msUntilTimeout = nextLatestAbsoluteTimeoutMs - currentTimeMs;
msUntilTimeout = msUntilTimeout < 0 ? 0 : msUntilTimeout;
// TODO: Account for the Just Noticeable Difference
const rootExpirationTime = root.expirationTime;
onSuspend(
root,
rootWorkInProgress,
suspendedExpirationTime,
rootExpirationTime,
msUntilTimeout,
);
return;
}
// Ready to commit.
onComplete(root, rootWorkInProgress, expirationTime);
比如说 if (didFatal) {}
,就是说有致命错误的时候,会执行 onFatal
if (didFatal) {
const didCompleteRoot = false;
stopWorkLoopTimer(interruptedBy, didCompleteRoot);
interruptedBy = null;
// There was a fatal error.
if (__DEV__) {
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) {}
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.
const didCompleteRoot = false;
stopWorkLoopTimer(interruptedBy, didCompleteRoot);
interruptedBy = null;
onYield(root);
return;
}
if(nextRenderDidError)
,会把提交放到第一优先级的任务上
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);
const suspendedExpirationTime = expirationTime;
const 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;
const suspendedExpirationTime = (root.nextExpirationTimeToWorkOn = expirationTime);
const rootExpirationTime = (root.expirationTime = Sync);
onSuspend(
root,
rootWorkInProgress,
suspendedExpirationTime,
rootExpirationTime,
-1, // Indicates no timeout
);
return;
}
}
if(hasLowerPriorityWork) {}
, 如果它是有一个低优先级的任务,还没有被更新的markSuspendedPriorityLevel
,这代表要把当前的这一次更新的内容去给它 suspend 了// packages/react-reconciler/src/ReactFiberScheduler.js#L1954
function onSuspend(
root: FiberRoot,
finishedWork: Fiber,
suspendedExpirationTime: ExpirationTime,
rootExpirationTime: ExpirationTime,
msUntilTimeout: number,
): void {
root.expirationTime = rootExpirationTime;
// !shouldYieldToRenderer() 表示任务还没有超时
// 并且 msUntilTimeout === 0 直接设置了 finishedwork
// 这个时候最终会直接调用 commitRoot
if (msUntilTimeout === 0 && !shouldYieldToRenderer()) {
// Don't wait an additional tick. Commit the tree immediately.
root.pendingCommitExpirationTime = suspendedExpirationTime;
root.finishedWork = finishedWork;
} else if (msUntilTimeout > 0) {
// Wait `msUntilTimeout` milliseconds before committing.
// 这个 scheduleTimeout 就是 window.setTimeout 方法
root.timeoutHandle = scheduleTimeout(
onTimeout.bind(null, root, finishedWork, suspendedExpirationTime),
msUntilTimeout,
);
}
}
onTimeout
function onTimeout(root, finishedWork, suspendedExpirationTime) {
// The root timed out. Commit it.
root.pendingCommitExpirationTime = suspendedExpirationTime;
root.finishedWork = finishedWork; // 注意,这里
// Read the current time before entering the commit phase. We can be
// certain this won't cause tearing related to batching of event updates
// because we're at the top of a timer event.
recomputeCurrentRendererTime();
currentSchedulerTime = currentRendererTime;
flushRoot(root, suspendedExpirationTime); // flushRoot 强制调用 commitRoot
}
flushRoot
function flushRoot(root: FiberRoot, expirationTime: ExpirationTime) {
invariant(
!isRendering,
'work.commit(): Cannot commit while already rendering. This likely ' +
'means you attempted to commit from inside a lifecycle method.',
);
// Perform work on root as if the given expiration time is the current time.
// This has the effect of synchronously flushing all work up to and
// including the given time.
nextFlushedRoot = root;
nextFlushedExpirationTime = expirationTime;
performWorkOnRoot(root, expirationTime, false); // 在这里面,存在 finishedWork 则直接调用 `completeRoot`
// Flush any sync work that was scheduled by lifecycles
performSyncWork();
}
// 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.
如果没有低优先级的任务,react 会直接发起一个新的同步更新
就是在 else if 下面, 这边的判断条件是比较苛刻的 else if(!root.didError && isYieldy) {}
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;
const suspendedExpirationTime = (root.nextExpirationTimeToWorkOn = expirationTime);
const rootExpirationTime = (root.expirationTime = Sync);
onSuspend(
root,
rootWorkInProgress,
suspendedExpirationTime,
rootExpirationTime,
-1, // Indicates no timeout
);
return;
}
root.expirationTime = (root.exirationTime = Sync)
root.nextExpirationTimeToWorkOn = expirationTime
const expirationTime = root.nextExpirationTimeToWorkOn
onSuspend
, 这里注意传入的是 -1,所以不会做任何的事情root.expirationTime = (root.exirationTime = Sync)
renderRoot(root, isYieldy); // 这边调用 renderRoot 返回了
finishedWork = root.finishedWork;
// 如果没有 finishedWork 就不会执行 completeRoot
if (finishedWork !== null) {
// We've completed the root. Commit it.
completeRoot(root, finishedWork, expirationTime);
}
performWorkOnRoot(
nextFlushedRoot,
nextFlushedExpirationTime,
currentRendererTime > nextFlushedExpirationTime,
);
findHighestPriorityRoot();
root.exirationTime = Sync
// packages/react-reconciler/src/ReactFiberScheduler.js#L1230
nextUnitOfWork = createWorkInProgress(
nextRoot.current,
null,
nextRenderExpirationTime,
);
对于Suspend 的来说,最大的一个情况,就是在下面这个情况下面, if (!isExpired && nextLatestAbsoluteTimeoutMs !== -1) {}
if (isYieldy && nextLatestAbsoluteTimeoutMs !== -1) {
// The tree was suspended.
const suspendedExpirationTime = expirationTime;
markSuspendedPriorityLevel(root, suspendedExpirationTime);
// Find the earliest uncommitted expiration time in the tree, including
// work that is suspended. The timeout threshold cannot be longer than
// the overall expiration.
const earliestExpirationTime = findEarliestOutstandingPriorityLevel(
root,
expirationTime,
);
const 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`.
const currentTimeMs = expirationTimeToMs(requestCurrentTime());
let msUntilTimeout = nextLatestAbsoluteTimeoutMs - currentTimeMs;
msUntilTimeout = msUntilTimeout < 0 ? 0 : msUntilTimeout;
// TODO: Account for the Just Noticeable Difference
const rootExpirationTime = root.expirationTime;
onSuspend(
root,
rootWorkInProgress,
suspendedExpirationTime,
rootExpirationTime,
msUntilTimeout,
);
return;
}
msUntilTimeout = msUntilTimeout < 0 ? 0 : msUntilTimeout
如果以上条件都不满足,不再发起新的同步更新,就会直接走到后面准备进入commit阶段
// Ready to commit.
onComplete(root, rootWorkInProgress, expirationTime);