记录学习过程,如有错误欢迎指出
书接上文,这篇文章讲解beginWork和completeWork
开始之前(必看)
React version 18.2.0
DEV代码可以忽略
,下面正文我使用省略号就代表是dev相关的代码,包含hydrate
字样的也请忽略,那是服务端渲染相关,望周知
我使用深度优先(🐶)的方式讲解:即遇到函数先进入函数,执行完函数后,又退回之前的函数.而不是在一个函数中讲完了再讲函数中执行的函数(希望能听懂我在说什么^v^)
因为使用了深度优先的方式讲解,
耦合比较重,不建议跳着看
performUnitOfWork(未完)
不管是
renderRootSync
还是renderRootConcurrent
最终都是进入了performUnitOfWork函数,接下来我们就看看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
// 递
//进入beginWork()
next = beginWork(current, unitOfWork, renderLanes);
// 停止profiler计时器
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
} else {
next = beginWork(current, unitOfWork, renderLanes);
}
.....
beginWork
let beginWork;
if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
const dummyFiber = null;
// 这里的beginWork相当于一个包装器
beginWork = (current, unitOfWork, lanes /*Nolane */) => {
// 如果一个组件抛出了一个错误,我们会在一个同步派发的事
// 件中再次重放它,这样调试器就会把它当作一个未捕获的错误来处理
// 在进入开始阶段之前,把正在进行的工作复制到一个假纤维上。如果beginWork抛出,我们将用它来重置状态。
const originalWorkInProgressCopy = assignFiberPropertiesInDEV(
dummyFiber,
unitOfWork
);
try {
// 真正的beginWork()
return originalBeginWork(current, unitOfWork, lanes);
//beginWork()出现错误才执行,没有出现错误就返回上一层函数了
} catch (originalError) {
if (
didSuspendOrErrorWhileHydratingDEV() ||
(originalError !== null &&
typeof originalError === "object" &&
typeof originalError.then === "function")
) {
throw originalError;
}
// 重置语境依赖
resetContextDependencies();
resetHooksAfterThrow();
// Don't reset current debug fiber, since we're about to work on the
// same fiber again.
// Unwind the failed stack frame
unwindInterruptedWork(current, unitOfWork, workInProgressRootRenderLanes);
// 将在3119行执行的assignFiberPropertiesInDEV恢复之前的状态
assignFiberPropertiesInDEV(unitOfWork, originalWorkInProgressCopy);
if (enableProfilerTimer && unitOfWork.mode & ProfileMode) {
// Reset the profiler timer.
startProfilerTimer(unitOfWork);
}
// 再一次执行beginWork()
// invokeGuardedCallback()帮助收集错误
invokeGuardedCallback(
null,
originalBeginWork,
null,
current,
unitOfWork,
lanes
);
......
}
};
} else {
beginWork = originalBeginWork;
}
真正的beginWork
beginWork分为两个判断一个update时,一个mount时,会执行不同的逻辑
update时
// 真正的beginWork()
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
.....
// 即mount时current === null
// 组件update时,由于之前已经mount过,所以current !== null。
// 所以我们可以通过current === null ?来区分组件是处于mount还是update
// update时:如果current存在可能存在优化路径,可以复用current(即上一次更新的Fiber节点)
// update时
if (current !== null) {
const oldProps = current.memoizedProps;// 上一次更新的props
const newProps = workInProgress.pendingProps; // 本次更新的props
// didReceiveUpdate 表示是否有新的props更新,有则会设置为true,没有则是false
if (
oldProps !== newProps ||
/*
hasLegacyContextChanged判断了是否有老版本context使用并且发生变化
react源码中存在一个valueStack和valueCursor用来记录context的历史信息和当前context,
另外还有一个didPerformWorkStackCursor用来表示当前的context有没有变化
hasLegacyContextChanged就会返回didPerformWorkStackCursor
*/
hasLegacyContextChanged() ||
// Force a re-render if the implementation changed due to hot reload:
(__DEV__ ? workInProgress.type !== current.type : false)
) {(memo).
didReceiveUpdate = true;
} else {
// checkScheduledUpdateOrContext函数检查当前fiber节点上的lanes是否存在于renderLanes中
// 存在则说明当前fiber节点需要更新,不存在则不需要更新则复用之前的节点
const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
current,
renderLanes,
);
if (
!hasScheduledUpdateOrContext &&
(workInProgress.flags & DidCapture) === NoFlags
) {
didReceiveUpdate = false;
// 复用之前的节点
return attemptEarlyBailoutIfNoScheduledUpdate(
current,
workInProgress,
renderLanes,
);
}
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
didReceiveUpdate = true;
} else {
didReceiveUpdate = false;
}
}
}
mount时
else {
// mount时
didReceiveUpdate = false;
if (getIsHydrating() && isForkedChild(workInProgress)) {
const slotIndex = workInProgress.index;
const numberOfForks = getForksAtLevel(workInProgress);
pushTreeId(workInProgress, numberOfForks, slotIndex);
}
}
后续
后续就是对不同类型的节点进行判断
workInProgress.lanes = NoLanes;
// 首先会根据不同 Fiber 节点的 tag,执行不同的 case,进入不同类型的 Fiber 子节点创建逻辑
switch (workInProgress.tag) {
case IndeterminateComponent: {
return mountIndeterminateComponent(
current,
workInProgress,
workInProgress.type,
renderLanes,
);
}
case LazyComponent: {
const elementType = workInProgress.elementType;
return mountLazyComponent(
current,
workInProgress,
elementType,
renderLanes,
);
}
case FunctionComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
case ClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
.......
);
}
performUnitOfWork(完)
next===null,说明comleteWork只会在beginWork完所以节点之后才会执行到
// 收集props
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// 如果没有了新的任务,就执行 "归" 操作
// 归
//进入completeUnitOfWork
completeUnitOfWork(unitOfWork);
} else {
// 反之继续 执行beginWork()
workInProgress = next;
}
ReactCurrentOwner.current = null;
}
completeUnitOfWork(完)
function completeUnitOfWork(unitOfWork: Fiber): void {
// 尝试完成当前单位的工作,然后转到下一个,兄弟节点。如果没有更多的兄弟节点,则返回到父fiber。
let completedWork = unitOfWork;
do {
// 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.
// 获取备份节点中的当前节点
const current = completedWork.alternate;
// 获取父节点 return就是指向父节点的
const returnFiber = completedWork.return;
// Check if the work completed or if something threw.
// Incomplete = 0b00000000001000000000000000;
// 没有异常
// flags就是标记,类似lane,也是级别
if ((completedWork.flags & Incomplete) === NoFlags) {
setCurrentDebugFiberInDEV(completedWork);
let next;
// 没有启动性能记录
if (
!enableProfilerTimer ||
(completedWork.mode & ProfileMode) === NoMode
) {
// 执行 completeWork
next = completeWork(current, completedWork, renderLanes);
} else {
// 反之开启性能记录
startProfilerTimer(completedWork);
// 再执行completeWork
// 在执行完completeWork()后next就是更新完后的节点
next = completeWork(current, completedWork, renderLanes);
// Update render duration assuming we didn't error.
stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
}
resetCurrentDebugFiberInDEV();
// 如果next存在,则表示产生了新 work
if (next !== null) {
// Completing this fiber spawned new work. Work on that next.
// 将workInProgress替换为新的树,以便后续更新
workInProgress = next;
return;
}
// 若是该 fiber 节点未能完成 work 的话(异常)
} else {
// This fiber did not complete because something threw. Pop values off
// the stack without entering the complete phase. If this is a boundary,
// capture values if possible.
// 因为节点未能完成更新,我们需要找到其中的错误
const next = unwindWork(current, completedWork, renderLanes);
// Because this fiber did not complete, don't reset its lanes.
// 如果next存在,则表示产生了新 work
if (next !== null) {
// If completing this work spawned new work, do that next. We'll come
// back here again.
// Since we're restarting, remove anything that is not a host effect
// from the effect tag.
// HostEffectMask = 0b00000000000111111111111111
next.flags &= HostEffectMask;
// 这里的逻辑和2076行一致,用新的替换旧的,就不需要return next出去了
workInProgress = next;
return;
}
// 由于该 fiber 未能完成,所以不必重置它的 expirationTime
if (
enableProfilerTimer &&
(completedWork.mode & ProfileMode) !== NoMode
) {
// Record the render duration for the fiber that errored.
stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
//虽然报错了,但仍然会累计 work 时长,但不重置expirationTime
let actualDuration = completedWork.actualDuration;
let child = completedWork.child;
while (child !== null) {
actualDuration += child.actualDuration;
child = child.sibling;
}
completedWork.actualDuration = actualDuration;
}
// 如果父节点存在的话,标记为「未完成」,并清除子树标记
if (returnFiber !== null) {
returnFiber.flags |= Incomplete;
returnFiber.subtreeFlags = NoFlags;
returnFiber.deletions = null;
} else {
// We've unwound all the way to the root.
// workInProgressRootExitStatus是缓存树退出时标记的状态
// RootDidNotComplete = 6
workInProgressRootExitStatus = RootDidNotComplete;
// 因为returnFiber不存在了,说明是root了,所以将缓存树清空,以便进行下一次更新
// 这里置为null的原因是,在workLoopSync()内while执行的条件是workInProgress !== null,那么置为null就结束while了
workInProgress = null;
return;
}
}
// 获取兄弟节点
const siblingFiber = completedWork.sibling;
// 如果兄弟节点存在,那么将缓存树替换为siblingFiber
if (siblingFiber !== null) {
// If there is more work to do in this returnFiber, do that next.
workInProgress = siblingFiber;
return;
}
//如果能执行到这一步的话,说明 siblingFiber 为 null,
//那么就返回至父节点
completedWork = returnFiber;
// Update the next thing we're working on in case something throws.
workInProgress = completedWork;
// 只要completedWork不为null,那么就会一直更新,直到更新完毕
} while (completedWork !== null);
// 走到这里说明已经执行到root了,判断缓存树退出状态是否RootInProgress(0)
if (workInProgressRootExitStatus === RootInProgress) {
// 如果是那么标记退出状态为RootCompleted(5) 即[已完成]
workInProgressRootExitStatus = RootCompleted;
}
}
可以看到completeWork成功执行完后会带着ExitStatus返回,这也让我们和之前performConcurrentWorkOnRoot对上了
//performConcurrentWorkOnRoot
let exitStatus = shouldTimeSlice
? renderRootConcurrent(root, lanes)
: renderRootSync(root, lanes);
performConcurrentWorkOnRoot(完)
对completeWork退出状态进行相应处理
.....
// 检查退出状态
if (exitStatus !== RootInProgress) {
// 是否出错
if (exitStatus === RootErrored) {
const errorRetryLanes = getLanesToRetrySynchronouslyOnError(root);
if (errorRetryLanes !== NoLanes) {
lanes = errorRetryLanes;
exitStatus = recoverFromConcurrentError(root, errorRetryLanes);
}
}
// 是否是死亡了
if (exitStatus === RootFatalErrored) {
const fatalError = workInProgressRootFatalError;
prepareFreshStack(root, NoLanes);
markRootSuspended(root, lanes);
ensureRootIsScheduled(root, now());
throw fatalError;
}
// 是否没有完成
if (exitStatus === RootDidNotComplete) {
markRootSuspended(root, lanes);
// 最后那就是渲染完成进入
} else {
const renderWasConcurrent = !includesBlockingLane(root, lanes);
const finishedWork: Fiber = (root.current.alternate: any);
if (
renderWasConcurrent &&
// isRenderConsistentWithExternalStores()就是判断store状态是否一致
// Concurrent模式下,需要进行 store 的一致性检查
!isRenderConsistentWithExternalStores(finishedWork)
) {
// 如果Concurrent模式下,store状态不一致,那么进行同步渲染
exitStatus = renderRootSync(root, lanes);
// 再次判断是否出错
if (exitStatus === RootErrored) {
const 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) {
const fatalError = workInProgressRootFatalError;
prepareFreshStack(root, NoLanes);
markRootSuspended(root, lanes);
ensureRootIsScheduled(root, now());
throw fatalError;
}
}
// 我们现在有一个一致的树。下一步是提交,或者,如果有事情暂停,则等待超时后再提交。
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
//!!!!!!
// commit入口
finishConcurrentRender(root, exitStatus, lanes);
//!!!!!!
}
}
// 再次进入ensureRootIsScheduled,确保根调度
ensureRootIsScheduled(root, now());
if (root.callbackNode === originalCallbackNode) {
// 为这个根安排的任务节点是目前正在执行的那个,需要返回一个 continuation.
return performConcurrentWorkOnRoot.bind(null, root);
}
return null;
}
总结
render阶段到此为止就结束了,下一阶段就是commit阶段了,入口位于
performConcurrentWorkOnRoot
的最后一段finishConcurrentRender(root, exitStatus, lanes);
,当然这篇文章因为篇幅有限,没有讲到双缓存和diff过程,等把commit阶段讲完后,我会将双缓存树和diff单独讲一下,但是大家在注释中应该能看见在哪开启双缓存树的
我也画完了React三个阶段流程图,后面我会上传到github,欢迎大家star
记录学习过程,如有错误欢迎指出