前言:
(1) 关于completeUnitOfWork()
在哪里使用到,请看下 React源码解析之workLoop 中的二、performUnitOfWork
(2) 本文需要了解的基础知识
① workInProgress
即fiber
对象,它的相关属性请看:
React源码解析之RootFiber
② 源码里A&B===C
的&
运算符的含义,请看 前端小知识10点 中的第八点
一、completeUnitOfWork
作用:
完成当前节点的work
,并赋值Effect
链,然后移动到兄弟节点,重复该操作,当没有更多兄弟节点时,返回至父节点,最终返回至root
节点
源码:
//完成当前节点的 work,然后移动到兄弟节点,重复该操作,当没有更多兄弟节点时,返回至父节点
function completeUnitOfWork(unitOfWork: Fiber): Fiber | null {
// Attempt to complete the current unit of work, then move to the next
// sibling. If there are no more siblings, return to the parent fiber.
//从下至上,移动到该节点的兄弟节点,如果一直往上没有兄弟节点,就返回父节点
//可想而知,最终会到达 root 节点
workInProgress = 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 = workInProgress.alternate;
//获取父节点
const returnFiber = workInProgress.return;
// Check if the work completed or if something threw.
//判断节点的操作是否完成,还是有异常丢出
//Incomplete表示捕获到该节点抛出的 error
//&是表示位的与运算,把左右两边的数字转化为二进制,然后每一位分别进行比较,如果相等就为1,不相等即为0
//如果该节点没有异常抛出的话,即可正常执行
if ((workInProgress.effectTag & Incomplete) === NoEffect) {
//dev 环境,可不看
setCurrentDebugFiberInDEV(workInProgress);
let next;
//如果不能使用分析器的 timer 的话,直接执行completeWork,
//否则执行分析器timer,并执行completeWork
if (
!enableProfilerTimer ||
(workInProgress.mode & ProfileMode) === NoMode
) {
//完成该节点的更新
next = completeWork(current, workInProgress, renderExpirationTime);
} else {
//启动分析器的定时器,并赋成当前时间
startProfilerTimer(workInProgress);
//完成该节点的更新
next = completeWork(current, workInProgress, renderExpirationTime);
// Update render duration assuming we didn't error.
//在没有报错的前提下,更新渲染持续时间
//记录分析器的timer的运行时间间隔,并停止timer
stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
}
//停止 work 计时,可不看
stopWorkTimer(workInProgress);
//dev 环境,可不看
resetCurrentDebugFiberInDEV();
//更新该节点的 work 时长和子节点的 expirationTime
resetChildExpirationTime(workInProgress);
//如果next存在,则表示产生了新 work
if (next !== null) {
// Completing this fiber spawned new work. Work on that next.
//返回 next,以便执行新 work
return next;
}
//如果父节点存在,并且其 Effect 链没有被赋值的话
if (
returnFiber !== null &&
// Do not append effects to parents if a sibling failed to complete
(returnFiber.effectTag & Incomplete) === NoEffect
) {
// Append all the effects of the subtree and this fiber onto the effect
// list of the parent. The completion order of the children affects the
// side-effect order.
//子节点的完成顺序会影响副作用的顺序
//如果父节点没有挂载firstEffect的话,将当前节点的firstEffect赋值给父节点的firstEffect
if (returnFiber.firstEffect === null) {
returnFiber.firstEffect = workInProgress.firstEffect;
}
//同上,根据当前节点的lastEffect,初始化父节点的lastEffect
if (workInProgress.lastEffect !== null) {
//如果父节点的lastEffect有值的话,将nextEffect赋值
//目的是串联Effect链
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
}
returnFiber.lastEffect = workInProgress.lastEffect;
}
// If this fiber had side-effects, we append it AFTER the children's
// side-effects. We can perform certain side-effects earlier if needed,
// by doing multiple passes over the effect list. We don't want to
// schedule our own side-effect on our own list because if end up
// reusing children we'll schedule this effect onto itself since we're
// at the end.
//获取副作用标记
const effectTag = workInProgress.effectTag;
// Skip both NoWork and PerformedWork tags when creating the effect
// list. PerformedWork effect is read by React DevTools but shouldn't be
// committed.
//如果该副作用标记大于PerformedWork
if (effectTag > PerformedWork) {
//当父节点的lastEffect不为空的时候,将当前节点挂载到父节点的副作用链的最后
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = workInProgress;
} else {
//否则,将当前节点挂载在父节点的副作用链的头-firstEffect上
returnFiber.firstEffect = workInProgress;
}
//无论父节点的lastEffect是否为空,都将当前节点挂载在父节点的副作用链的lastEffect上
returnFiber.lastEffect = workInProgress;
}
}
}
//如果该 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(workInProgress, renderExpirationTime);
// Because this fiber did not complete, don't reset its expiration time.
//由于该 fiber 未能完成,所以不必重置它的 expirationTime
if (
enableProfilerTimer &&
(workInProgress.mode & ProfileMode) !== NoMode
) {
// Record the render duration for the fiber that errored.
//记录分析器的timer的运行时间间隔,并停止timer
stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
// Include the time spent working on failed children before continuing.
//虽然报错了,但仍然会累计 work 时长
let actualDuration = workInProgress.actualDuration;
let child = workInProgress.child;
while (child !== null) {
actualDuration += child.actualDuration;
child = child.sibling;
}
workInProgress.actualDuration = actualDuration;
}
//如果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.
// TODO: The name stopFailedWorkTimer is misleading because Suspense
// also captures and restarts.
//停止失败的 work 计时,可不看
stopFailedWorkTimer(workInProgress);
//更新其 effectTag,标记是 restart 的
next.effectTag &= HostEffectMask;
//返回 next,以便执行新 work
return next;
}
//停止 work 计时,可不看
stopWorkTimer(workInProgress);
//如果父节点存在的话,重置它的 Effect 链,标记为「未完成」
if (returnFiber !== null) {
// Mark the parent fiber as incomplete and clear its effect list.
returnFiber.firstEffect = returnFiber.lastEffect = null;
returnFiber.effectTag |= Incomplete;
}
}
//获取兄弟节点
const siblingFiber = workInProgress.sibling;
if (siblingFiber !== null) {
// If there is more work to do in this returnFiber, do that next.
return siblingFiber;
}
// Otherwise, return to the parent
//如果能执行到这一步的话,说明 siblingFiber 为 null,
//那么就返回至父节点
workInProgress = returnFiber;
} while (workInProgress !== null);
// We've reached the root.
//当执行到这里的时候,说明遍历到了 root 节点,已完成遍历
//更新workInProgressRootExitStatus的状态为「已完成」
if (workInProgressRootExitStatus === RootIncomplete) {
workInProgressRootExitStatus = RootCompleted;
}
return null;
}
解析:
① 整体上看是一个大的while
循环:
从当前节点开始,遍历到兄弟节点,当无兄弟节点时,返回至父节点,
再从父节点开始,遍历到兄弟节点,当无兄弟节点时,返回至父父节点,
可想而知,最终会返回至rootFiber
节点
② 当整颗 fiber 树遍历完成后,更新workInProgressRootExitStatus
的状态为「已完成」
我们来看一下do...while
内部的逻辑:
(1) 如果该节点可正常执行的话
① 直接执行completeWork()
方法,更新该节点(从fiber
对象转变成真实的DOM
节点)
ps:下篇解析该方法
② 如果可以启用ProfilerTimer
的话,则执行startProfilerTimer()
和stopProfilerTimerIfRunningAndRecordDelta()
,用来记录fiber
节点执行work
的实际开始时间(actualStartTime
)和work
时长(actualDuration
)
详细解析请看本文的「二、startProfilerTimer和stopProfilerTimerIfRunningAndRecordDelta」
③ stopWorkTimer()
的作用是停止work
计时,不是很重要,可不看
④ resetChildExpirationTime
的作用是更新该节点的work
时长和获取优先级最高的子节点的expirationTime
详细解析请看本文的「三、resetChildExpirationTime」
⑤ 如果next
存在,则表示该节点在这次更新完成后,产生了新的更新,那么就返回该next
,并将其作为completeUnitOfWork()
的参数,再次执行
⑥ 接下来这一段比较重要,是 Effect 链的赋值,看个例子:
假设Span1
有更新,Span2
也有更新
那么父节点DIV
的firstEffect
和lastEffect
在Span1
执行completeUnitOfWork()
后,会是下面这个样子:
workInProgress1
即表示Span1
对应的fiber
对象
当轮到Span2
执行completeUnitOfWork()
后,又会变成下面这个样子:
也就是说:Effect链是帮助父节点简单判断子节点是否有更新及更新顺序的
⑦ else
的情况就是执行更新的过程中捕获到error
的情况,此时执行的是unwindWork
而不是completeWork
,与completeWork
最大的区别是有ShouldCapture
的判断,也是后续文章会讲到
else
后面的逻辑跟上面大同小异了,不再赘述
之后是遍历兄弟节点,返回父节点,再次遍历,不再赘述
⑧ 可以看到,completeUnitOfWork
主要做了三件事:
(1) 执行completeWork
,完成节点更新
(2) 执行resetChildExpirationTime
,获取优先级最高的childExpirationTime
(3) 赋值Effect
链
二、startProfilerTimer和stopProfilerTimerIfRunningAndRecordDelta
作用:
记录fiber
节点执行work
的实际开始时间(actualStartTime
)和work
时长
源码:startProfilerTimer()
:
//启动分析器的timer,并赋成当前时间
function startProfilerTimer(fiber: Fiber): void {
//如果不能启动分析器的timer的话,就 return
if (!enableProfilerTimer) {
return;
}
//分析器的开始时间
profilerStartTime = now();
//如果 fiber 节点的实际开始时间 < 0 的话,则赋成当前时间
if (((fiber.actualStartTime: any): number) < 0) {
fiber.actualStartTime = now();
}
}
stopProfilerTimerIfRunningAndRecordDelta()
:
//记录分析器的timer的work 时间,并停止timer
function stopProfilerTimerIfRunningAndRecordDelta(
fiber: Fiber,
overrideBaseTime: boolean,
): void {
//如果不能启动分析器的定时器的话,就 return
if (!enableProfilerTimer) {
return;
}
//如果分析器的开始时间>=0的话
if (profilerStartTime >= 0) {
//获取运行的时间间隔
const elapsedTime = now() - profilerStartTime;
//累计实际 work 时间间隔
fiber.actualDuration += elapsedTime;
if (overrideBaseTime) {
//记录时间间隔
fiber.selfBaseDuration = elapsedTime;
}
//上述操作完成后,将分析器的timer的开始时间重置为-1
profilerStartTime = -1;
}
}
解析:
逻辑比较简单,就不额外补充了
三、resetChildExpirationTime
作用:
更新该节点的work
时长和获取优先级最高的子节点的expirationTime
源码:
//更新该节点的 work 时长和获取优先级最高的子节点的 expirationTime
function resetChildExpirationTime(completedWork: Fiber) {
//如果当前渲染的节点需要更新,但是子节点不需要更新的话,则 return
if (
renderExpirationTime !== Never &&
completedWork.childExpirationTime === Never
) {
// The children of this component are hidden. Don't bubble their
// expiration times.
return;
}
let newChildExpirationTime = NoWork;
// Bubble up the earliest expiration time.
if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
// In profiling mode, resetChildExpirationTime is also used to reset
// profiler durations.
//获取当前节点的实际 work 时长
let actualDuration = completedWork.actualDuration;
//获取 fiber 树的 work 时长
let treeBaseDuration = completedWork.selfBaseDuration;
// When a fiber is cloned, its actualDuration is reset to 0. This value will
// only be updated if work is done on the fiber (i.e. it doesn't bailout).
// When work is done, it should bubble to the parent's actualDuration. If
// the fiber has not been cloned though, (meaning no work was done), then
// this value will reflect the amount of time spent working on a previous
// render. In that case it should not bubble. We determine whether it was
// cloned by comparing the child pointer.
// 当一个 fiber 节点被克隆后,它的实际 work 时长被重置为 0.
// 这个值只会在 fiber 自身上的 work 完成时被更新(顺利执行的话)
// 当 fiber 自身 work 完成后,将自身的实际 work 时长冒泡赋给父节点的实际 work 时长
// 如果 fiber 没有被克隆,即 work 未被完成的话,actualDuration 反映的是上次渲染的实际 work 时长
// 如果是这种情况的话,不应该冒泡赋给父节点
// React 通过比较 子指针 来判断 fiber 是否被克隆
// 关于 alternate 的作用,请看:https://juejin.cn/post/6844903919588474888
// 是否将 work 时间冒泡至父节点的依据是:
// (1) 该 fiber 节点是否是第一次渲染
// (2) 该 fiber 节点的子节点有更新
const shouldBubbleActualDurations =
completedWork.alternate === null ||
completedWork.child !== completedWork.alternate.child;
//获取当前节点的第一个子节点
let child = completedWork.child;
//当该子节点存在时,通过newChildExpirationTime来获取子节点、子子节点两者中优先级最高的那个expirationTime
while (child !== null) {
//获取该子节点的 expirationTime
const childUpdateExpirationTime = child.expirationTime;
//获取该子节点的 child 的 expirationTime
const childChildExpirationTime = child.childExpirationTime;
//如果子节点的优先级大于NoWork的话,则将newChild的 expirationTime 赋值为该子节点的 expirationTime
if (childUpdateExpirationTime > newChildExpirationTime) {
newChildExpirationTime = childUpdateExpirationTime;
}
//子节点的 child 同上
if (childChildExpirationTime > newChildExpirationTime) {
newChildExpirationTime = childChildExpirationTime;
}
if (shouldBubbleActualDurations) {
//累计子节点的 work 时长
actualDuration += child.actualDuration;
}
//累计 fiber 树的 work 时长
treeBaseDuration += child.treeBaseDuration;
//移动到兄弟节点,重复上述过程
child = child.sibling;
}
//更新 fiber 的 work 时长
completedWork.actualDuration = actualDuration;
//更新 fiber 树的 work 时长
completedWork.treeBaseDuration = treeBaseDuration;
}
//逻辑同上,不再赘述
else {
let child = completedWork.child;
while (child !== null) {
const childUpdateExpirationTime = child.expirationTime;
const childChildExpirationTime = child.childExpirationTime;
if (childUpdateExpirationTime > newChildExpirationTime) {
newChildExpirationTime = childUpdateExpirationTime;
}
if (childChildExpirationTime > newChildExpirationTime) {
newChildExpirationTime = childChildExpirationTime;
}
child = child.sibling;
}
}
completedWork.childExpirationTime = newChildExpirationTime;
}
解析:
(1) 将累计的子节点的work
时长冒泡赋值到父节点的actualDuration
上
(2) 循环遍历目标节点的子节点们,将子节点中优先级最高的expirationTime
更新到目标及诶按的childExpirationTime
上
(3) 关于childExpirationTime
的详细解释,请看:
React之childExpirationTime
四、GitHub
github.com/AttackXiaoJ…
(完)