05-1-渲染流程-performConcurrentWorkOnRoot
1
两种模式会进入performSyncWorkOnRoot或者performConcurrentWorkOnRoot, 这两个方法分别会调用workLoopSync或者workLoopConcurrent
scheduleCallback 进行对第二个参数进行调度,performConcurrentWorkOnRoot.bind(null, root) 最终会在某个时机被执行
/*
在 ensureRootIsScheduled 中,如果 getHighestPriorityLane 调用的返回值不是 SyncLane的时候,则有以下代码:
*/
function ensureRootIsScheduled(){
// ...
newCallbackNode = scheduleCallback(schedulerPriorityLevel,performConcurrentWorkOnRoot.bind(null, root))
root.callbackNode = newCallbackNode
}
两个函数区别 workLoopConcurrent workLoopSync
它们唯一的区别是是否调用shouldYield。如果当前浏览器帧没有剩余时间,shouldYield会中止循环,直到浏览器有空闲时间后再继续遍历。
workInProgress代表当前已创建的 workInProgress fiber。
function workLoopConcurrent() {
// Perform work until Scheduler asks us to yield
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
function workLoopSync() {
// Already timed out, so perform work without checking if we need to yield.
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
函数内执行的关键函数
- flushPassiveEffects :执行某些副作用
- ensureRootIsScheduled :形成递归执行
- renderRootConcurrent :与 renderRootSync 只能两选一,如果执行的是renderRootConcurrent ,最后会执行 finishConcurrentRender
- renderRootSync : 与 renderRootConcurrent 二选一
- finishConcurrentRender :执行完成并发渲染的后置操作(我估计是这样的)
function performConcurrentWorkOnRoot(root, didTimeout) {
{
resetNestedUpdateFlag();
} // Since we know we're in a React event, we can clear the current
// event time. The next update will compute a new event time.
console.log('performConcurrentWorkOnRoot:同步更新concurrent模式');
currentEventTime = NoTimestamp;
currentEventTransitionLane = NoLanes;
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
throw new Error('Should not already be working.');
} // Flush any pending passive effects before deciding which lanes to work on,
// in case they schedule additional work.
var originalCallbackNode = root.callbackNode;
var didFlushPassiveEffects = flushPassiveEffects();
if (didFlushPassiveEffects) {
// Something in the passive effect phase may have canceled the current task.
// Check if the task node for this root was changed.
if (root.callbackNode !== originalCallbackNode) {
// The current task was canceled. Exit. We don't need to call
// `ensureRootIsScheduled` because the check above implies either that
// there's a new task, or that there's no remaining work on this root.
return null;
}
} // Determine the next lanes to work on, using the fields stored
// on the root.
var lanes = getNextLanes(root, root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes);
if (lanes === NoLanes) {
// Defensive coding. This is never expected to happen.
return null;
} // We disable time-slicing in some cases: if the work has been CPU-bound
// for too long ("expired" work, to prevent starvation), or we're in
// sync-updates-by-default mode.
// TODO: We only check `didTimeout` defensively, to account for a Scheduler
// bug we're still investigating. Once the bug in Scheduler is fixed,
// we can remove this, since we track expiration ourselves.
var shouldTimeSlice = !includesBlockingLane(root, lanes) && !includesExpiredLane(root, lanes) && (!didTimeout);
var exitStatus = shouldTimeSlice ? renderRootConcurrent(root, lanes) : renderRootSync(root, lanes);
if (exitStatus !== RootInProgress) {
if (exitStatus === RootErrored) {
// 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.
var errorRetryLanes = getLanesToRetrySynchronouslyOnError(root);
if (errorRetryLanes !== NoLanes) {
lanes = errorRetryLanes;
exitStatus = recoverFromConcurrentError(root, errorRetryLanes);
}
}
if (exitStatus === RootFatalErrored) {
var fatalError = workInProgressRootFatalError;
prepareFreshStack(root, NoLanes);
markRootSuspended$1(root, lanes);
ensureRootIsScheduled(root, now());
throw fatalError;
}
if (exitStatus === RootDidNotComplete) {
// The render unwound without completing the tree. This happens in special
// cases where need to exit the current render without producing a
// consistent tree or committing.
//
// This should only happen during a concurrent render, not a discrete or
// synchronous update. We should have already checked for this when we
// unwound the stack.
markRootSuspended$1(root, lanes);
} else {
// The render completed.
// Check if this render may have yielded to a concurrent event, and if so,
// confirm that any newly rendered stores are consistent.
// TODO: It's possible that even a concurrent render may never have yielded
// to the main thread, if it was fast enough, or if it expired. We could
// skip the consistency check in that case, too.
var renderWasConcurrent = !includesBlockingLane(root, lanes);
var finishedWork = root.current.alternate;
if (renderWasConcurrent && !isRenderConsistentWithExternalStores(finishedWork)) {
// A store was mutated in an interleaved event. Render again,
// synchronously, to block further mutations.
exitStatus = renderRootSync(root, lanes); // We need to check again if something threw
if (exitStatus === RootErrored) {
var _errorRetryLanes = getLanesToRetrySynchronouslyOnError(root);
if (_errorRetryLanes !== NoLanes) {
lanes = _errorRetryLanes;
exitStatus = recoverFromConcurrentError(root, _errorRetryLanes); // We assume the tree is now consistent because we didn't yield to any
// concurrent events.
}
}
if (exitStatus === RootFatalErrored) {
var _fatalError = workInProgressRootFatalError;
prepareFreshStack(root, NoLanes);
markRootSuspended$1(root, lanes);
ensureRootIsScheduled(root, now());
throw _fatalError;
}
} // We now have a consistent tree. The next step is either to commit it,
// or, if something suspended, wait to commit it after a timeout.
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
finishConcurrentRender(root, exitStatus, lanes);
}
}
ensureRootIsScheduled(root, now());
if (root.callbackNode === originalCallbackNode) {
// The task node scheduled for this root is the same one that's
// currently executed. Need to return a continuation.
return performConcurrentWorkOnRoot.bind(null, root);
}
return null;
}
renderRootConcurrent
这个函数实现的逻辑主要如下:
- 记录原来的 executionContext, 然后给 executionContext 增加渲染上下文 RenderContext
- 保存现场——将 dispatcher 存入栈
- 如果有问题则重置 timer,并 prepareFreshStack
- 执行一看就很关键的 workLoopConcurrent
- 重置上下文依赖,即调用 resetContextDependencies
- 回复现场——从栈中 pop dispatcher
- 恢复原来的 executionContext
- 如果 workInProgress 不为 null,然后返回 RootIncomplete 状态,否则重置 workInProgressRoot 和 workInProgressRootRenderLanes,返回 workInProgressRootExitStatus 状态
function renderRootConcurrent(root, lanes) {
var prevExecutionContext = executionContext;
executionContext |= RenderContext;
var prevDispatcher = pushDispatcher(); // If the root or lanes have changed, throw out the existing stack
// and prepare a fresh one. Otherwise we'll continue where we left off.
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
{
if (isDevToolsPresent) {
var memoizedUpdaters = root.memoizedUpdaters;
if (memoizedUpdaters.size > 0) {
restorePendingUpdaters(root, workInProgressRootRenderLanes);
memoizedUpdaters.clear();
} // At this point, move Fibers that scheduled the upcoming work from the Map to the Set.
// If we bailout on this work, we'll move them back (like above).
// It's important to move them now in case the work spawns more work at the same priority with different updaters.
// That way we can keep the current update and future updates separate.
movePendingFibersToMemoized(root, lanes);
}
}
workInProgressTransitions = getTransitionsForLanes();
resetRenderTimer();
prepareFreshStack(root, lanes);
}
{
markRenderStarted(lanes);
}
do {
try {
workLoopConcurrent();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
resetContextDependencies();
popDispatcher(prevDispatcher);
executionContext = prevExecutionContext;
if (workInProgress !== null) {
// Still work remaining.
{
markRenderYielded();
}
return RootInProgress;
} else {
// Completed the tree.
{
markRenderStopped();
} // Set this to null to indicate there's no in-progress render.
workInProgressRoot = null;
workInProgressRootRenderLanes = NoLanes; // Return the final exit status.
return workInProgressRootExitStatus;
}
}
workLoopConcurrent
作用: 持续调用 performUnitOfWork 直到 workInProgress 为空或者 shouldYield 函数返回 false。
shouldYield 就是 scheduler 库里的 unstable_shouldYield 函数,该函数的作用是当处于需要中断 react的渲染,将控制交还给“主线程”的场景时返回 true,具体来说就是当前时间距离全局变量 startTime 的间隔小于帧最小间隔(约为 5ms)的时候返回 true。
performUnitOfWork
主要是执行 beginWork 逻辑,然后将 workInProgress.pedingProps 的内容复制给 workInProgress.memoizedProps, 最后 ReactCurrentOwner.current 重置为 null。
function performUnitOfWork (unitOfWork: Fiber): void {
const current = unitOfWork.alternate
setCurrentDebugFiberInDEV(unitOfWork)
let next = beginWork(current, unitOfWork, subtreeRenderLanes)
unitOfWork.memoizedProps = unitOfWork.pendingProps
if (next === null) {
completeUnitOfWork(unitOfWork)
} else {
workInProgress = next
}
ReactCurrentOwner.current = null
}
beginWork
直到现在,我们仍没看到 react 是如何实现 dom 的更新的,dom diff 又是如何进行的,而这部分逻辑隐藏在 beginWork 之下,明天我们再来瞅一瞅 beginWork 的实现。