记录学习过程,如有错误欢迎指出
前三篇文章我们已经讲完了React的初始化阶段,接下来就是render阶段了,这也是React核心调度开始
开始之前(必看)
React version 18.2.0
DEV代码可以忽略,下面正文我使用省略号就代表是dev相关的代码,包含hydrate字样的也请忽略,那是服务端渲染相关,望周知
我使用深度优先(🐶)的方式讲解:即遇到函数先进入函数,执行完函数后,又退回之前的函数.而不是在一个函数中讲完了再讲函数中执行的函数(希望能听懂我在说什么^v^)
因为使用了深度优先的方式讲解,
耦合比较重,不建议跳着看
入口
//performSyncWorkOnRoot就是在sync模式下的入口
scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
//performConcurrentWorkOnRoot就是是concurrent模式下的入口
newCallbackNode = scheduleCallback( schedulerPriorityLevel, // concurrent模式下调用 performConcurrentWorkOnRoot.bind(null, root), );
怎么知道什么时候会调用什么模式呢?
//ensureRootIsScheduled函数
//主要是靠newCallbackPriority优先级来判断,如果是SyncLane那么就是同步模式,反之就是并发模式
if (newCallbackPriority === SyncLane) {
....
scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
.....
}else{
.....
newCallbackNode = scheduleCallback( schedulerPriorityLevel, // concurrent模式下调用 performConcurrentWorkOnRoot.bind(null, root), );
.....
}
performConcurrentWorkOnRoot(未完)
我们先看Concurrent吧
function performConcurrentWorkOnRoot(root/*FiberRoot*/, didTimeout/*false*/) {
....
//重置
currentEventTime = NoTimestamp;
currentEventTransitionLane = NoLanes;
....
// 在决定在哪个车道上工作之前,先冲掉任何执行的被动效果。
// 以防他们(被动效果)安排额外的工作
const originalCallbackNode = root.callbackNode;
// flushPassiveEffects()冲洗被动效果
// 进入flushPassiveEffects()
const didFlushPassiveEffects = flushPassiveEffects();
....
flushPassiveEffects
export function flushPassiveEffects(): boolean {
// 如果 rootWithPendingPassiveEffects 存在,说明
// 使用了 useEffect 或者有子节点被删除
if (rootWithPendingPassiveEffects !== null) {
//缓存根部,因为rootWithPendingPassiveEffects会在flushPassiveEffectsImpl进行清空
const root = rootWithPendingPassiveEffects;
// 缓存并清除剩余的车道标志;它必须被重置,因为这个方法可以从不同的地方被调用,而不总是从 commitRoot
const remainingLanes = pendingPassiveEffectsRemainingLanes;
pendingPassiveEffectsRemainingLanes = NoLanes;
//下面几个优先级转换方法我就不一一讲解了,有兴趣可以自己看看
// 将lane转换为事件优先级
const renderPriority = lanesToEventPriority(pendingPassiveEffectsLanes);
// 取得最低事件优先级
const priority = lowerEventPriority(DefaultEventPriority, renderPriority);
// 保存transition
const prevTransition = ReactCurrentBatchConfig.transition;
// 保存当前更新优先级
const previousPriority = getCurrentUpdatePriority();
try {
ReactCurrentBatchConfig.transition = null;
// 设置当前更新优先级会获取的最低事件优先级
setCurrentUpdatePriority(priority);
// 进入flushPassiveEffectsImpl()
return flushPassiveEffectsImpl();
} finally {
// 恢复优先级
setCurrentUpdatePriority(previousPriority);
// 恢复transition
ReactCurrentBatchConfig.transition = prevTransition;
// Once passive effects have run for the tree - giving components a
// chance to retain cache instances they use - release the pooled
// cache at the root (if there is one)
releaseRootPooledCache(root, remainingLanes);
}
}
return false;
}
可以看出flushPassiveEffects的作用即是将被动效果全部消除
performConcurrentWorkOnRoot(未完)
回到performConcurrentWorkOnRoot
.....
//这里的didFlushPassiveEffects就是flushPassiveEffects()返回的结果,结果为false,所以不执行
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;
} else {
// Current task was not canceled. Continue.
}
}
// 使用存储在根上的字段,确定下一个工作通道的字段
let lanes = getNextLanes(
root,
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
);
// 永远不会执行
if (lanes === NoLanes) {
// Defensive coding. This is never expected to happen.
return null;
}
const shouldTimeSlice =
!includesBlockingLane(root, lanes) &&
!includesExpiredLane(root, lanes) &&
(disableSchedulerTimeoutInWorkLoop || !didTimeout);
//重点来了!!!
//重点来了!!!
// shouldTimeSlice函数根据lane的优先级,决定是使用并发模式还是同步模式渲染(解决饥饿问题)
// 走Sync还是Concurrent是根据shouldTimeSlice的结果来判断
// renderRootConcurrent和renderRootSync会触发不同的渲染逻辑
//我们先假设这里是进入的renderRootConcurrent()
let exitStatus = shouldTimeSlice
? renderRootConcurrent(root, lanes)
: renderRootSync(root, lanes);
renderRootConcurrent(未完)
function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
// 保存执行上下文
const prevExecutionContext = executionContext;
// 给 executionContext 增加渲染上下文 RenderContext
executionContext |= RenderContext;
// 保存现场——将 dispatcher 存入栈
const prevDispatcher = pushDispatcher();
//如果根或车道发生了变化,扔掉现有的堆栈并准备一个新的,否则退出
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
if (enableUpdaterTracking) {
if (isDevToolsPresent) {
const 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(root, lanes);
// 重置 timer
resetRenderTimer();
// 准备一个新的栈
// 作用:创建workInProgress树
prepareFreshStack(root, lanes);
}
....
//重点来了!!!
//重点来了!!!
//循环条件为永真,意味着只有正常执行完workLoopConcurrent才会break,一旦
//workLoopConcurrent中发生错误,那么将会继续循环,直到成功执行完毕workLoopConcurrent
do {
try {
// 重点
//我们进入workLoopConcurrent看看是什么
workLoopConcurrent();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
workLoopConcurrent
function workLoopConcurrent() {
// 执行工作,直到Scheduler要求放弃
// shouldYield()就是关键所在
// shouldYield为控制performUnitOfWork是否继续执行的开关,而performUnitOfWork是由React自己实现的RequestIdleCallback来控制的
while (workInProgress !== null && !shouldYield()) {
//进入performUnitOfWork
performUnitOfWork(workInProgress);
}
}
performUnitOfWork(未完)
function performUnitOfWork(unitOfWork: Fiber /*workInProgress */): void {
// 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.
// unitOfWork.alternate是节点副本和缓存树对象是一样的
const current = unitOfWork.alternate;
setCurrentDebugFiberInDEV(unitOfWork);
let next;
// export const ProfileMode = /* */ 0b000010;
// export const NoMode = /* */ 0b000000;
// 0b000000转换为boolean值是false
// 无论如何都会执行beginWork()的
// 只不过如果是true的情况会记录时间
if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
startProfilerTimer(unitOfWork);
//renderLanes = NoLanes
// 递
next = beginWork(current, unitOfWork, renderLanes);
// 停止profiler计时器
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
} else {
next = beginWork(current, unitOfWork, renderLanes);
}
// dev不管
resetCurrentDebugFiberInDEV();
// 收集props
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// 如果没有了新的任务,就执行 "归" 操作
// 归
completeUnitOfWork(unitOfWork);
} else {
// 反之继续 执行beginWork()
workInProgress = next;
}
ReactCurrentOwner.current = null;
}
可以看到内部调用了beginWork和completeUnitOfWork两个方法,可以将beginWork看做是递的操作,而complete是归的操作
这里我们暂时返回renderRootConcurrent,后面会继续讲
renderRootConcurrent(完)
....
// 重置上下文依赖
resetContextDependencies();
// 恢复现场——从栈中 pop dispatcher
popDispatcher(prevDispatcher);
// 恢复原来的执行上下文
executionContext = prevExecutionContext;
....
// 检查该树是否已经完成
if (workInProgress !== null) {
//如果仍有剩余的工作
if (enableSchedulingProfiler) {
// 继续
markRenderYielded();
}
// RootInProgress表示正在进行中
return RootInProgress;
} else {
// 完成了该树
if (enableSchedulingProfiler) {
// 标记 渲染停止
markRenderStopped();
}
// 将此设置为空,表示没有正在进行的渲染
workInProgressRoot = null;
workInProgressRootRenderLanes = NoLanes;
// 返回退出状态
// 这里将返回退出状态!!!
return workInProgressRootExitStatus;
}
}
performConcurrentWorkOnRoot(未完)
....
//刚才我们将renderRootConcurrent的内部逻辑梳理了一下,renderRootConcurrent就是内部
//循环调用workLoopConcurrent,workLoopConcurrent内循环执行performUnitOfWork,
//而shouldYield()控制着循环次数,当performUnitOfWork执行完毕后workLoopConcurrent会
//返回一个退出状态给exitStatus,performUnitOfWork内部就是beginWork和compleate的逻辑
let exitStatus = shouldTimeSlice
? renderRootConcurrent(root, lanes)
//现在我们再进入renderRootSync看看
: renderRootSync(root, lanes);
....
renderRootSync
可以看出renderRootSync和renderRootConcurrent不能说差不多,只能说一模一样,其中不同的地方就在于那个永真循环调用的是workLoopSync();,而renderRootConcurrent调用的是workLoopConcurrent
function renderRootSync(root: FiberRoot, lanes: Lanes) {
// 保存当前执行上下文
const prevExecutionContext = executionContext;
// 把当前执行上下文替换为render上下文
executionContext |= RenderContext;
const prevDispatcher = pushDispatcher();
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
if (enableUpdaterTracking) {
if (isDevToolsPresent) {
const memoizedUpdaters = root.memoizedUpdaters;
if (memoizedUpdaters.size > 0) {
restorePendingUpdaters(root, workInProgressRootRenderLanes);
memoizedUpdaters.clear();
}
movePendingFibersToMemoized(root, lanes);
}
}
// getTransitionsForLanes()获取一下root上的transitions
workInProgressTransitions = getTransitionsForLanes(root, lanes);
// 在该函数内开启双缓存树
prepareFreshStack(root, lanes);
}
if (__DEV__) {
if (enableDebugTracing) {
logRenderStarted(lanes);
}
}
if (enableSchedulingProfiler) {
markRenderStarted(lanes);
}
do {
try {
// 同步工作循环
//不同的地方!!!!
//不同的地方!!!!
//现在我们进入workLoopSync看看和workLoopConcurrent又有什么不同
workLoopSync();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
/**
* 这里是永真,如果workLoopSync()是抛出错误,那么
* 将不会执行到break,所以还得再执行一次workLoopSync()
* 如果workLoopSync()是正常执行完毕,那么就会依次执行break,就
* 退出永真循环了
*/
} while (true);
resetContextDependencies();
executionContext = prevExecutionContext;
popDispatcher(prevDispatcher);
if (workInProgress !== null) {
// This is a sync render, so we should have finished the whole tree.
throw new Error(
'Cannot commit an incomplete root. This error is likely caused by a ' +
'bug in React. Please file an issue.',
);
}
if (__DEV__) {
if (enableDebugTracing) {
logRenderStopped();
}
}
if (enableSchedulingProfiler) {
markRenderStopped();
}
//将此设置为空,表示没有正在进行的渲染
workInProgressRoot = null;
workInProgressRootRenderLanes = NoLanes;
return workInProgressRootExitStatus;
}
workLoopSync
function workLoopSync() {
//不同之处就在于这个循环条件没有了shouldYield()
while (workInProgress !== null) {
// performUnitOfWork方法会创建下一个Fiber节点并赋值给workInProgress,
// 并将workInProgress与已创建的Fiber节点连接起来构成Fiber树
// performUnitOfWork的工作可以分为两部分:“递”和“归”
performUnitOfWork(workInProgress);
}
}
没必要再进入performUnitOfWork内看了吧,所以说不管是workLoopConcurrent还是workLoopSync其内部都是调用了performUnitOfWork,只不过workLoopConcurrent会判断shouldYield(),基于shouldYield()来看是否还没有时间继续执行,如果还有剩余的时间,那么还会执行,而workLoopSync就是一股脑执行完,不管是否还有剩余时间
总结
这篇文章暂时只讲到这里,希望有不懂react为什么会有shouldYield()的同学可以去看看其他文章,这样有利于理解,下一次篇文章,我会继续讲到beginWork和completeWork,和另外一个入口方法performSyncWorkOnRoot
记录学习过程,如有错误欢迎指出