10 KiB
beginWork 执行在递归节点的 Fiber 创建之前,主要是为传入的 Fiber 节点根据类型创建第一个子 Fiber 节点
function beginWork(current, workInProgress, renderLanes) {
// 前 beginWork 阶段
if (current !== null) {
......
} else {
......
}
// 正式 beginWork 阶段
switch (workInProgress.tag) {
......
}
}
beginWork 函数接受三个参数,分别是 current 节点,workInProgress 节点和 renderLanes 优先级,根据 beginWork 函数的结构,我们可以分成两个阶段,分别是:前 beginWork 阶段和正式 beginWork 阶段
前 beginWork 阶段
if (current !== null) {
......
} else {
didReceiveUpdate = false;
}
在进入正式 beginWork 阶段之前,会先对传入的 current 节点进行空值判断,根据 current 是否为空进入不同的处理逻辑。
那么为什么需要判断 current 是否为空呢?答案是为了性能,前面说到 beginWork 函数的主要任务就是给当前传入的 Fiber 节点创建它的第一个子 Fiber 节点,要是在上次更新和本次更新中当前的 Fiber 节点并没有发生变化,那么还需要再次创建一个新的 Fiber 节点吗?肯定是不需要的,所以我们只需要将这个已经存在的并没有发生变化的 Fiber 节点拿过来复用就行了,而这段逻辑正是在前 beginWork 阶段中判断并执行的 这一段代码的主要目的是为了赋值 didReceiveUpdate 变量,这个变量表示在本次更新中当前 current 节点是否存在变化
current 不为空
在 current 不为空的逻辑中,会先取出先前存储在 Fiber 节点中的新旧 props,连同其他几个判断条件一起做 if 判断,判断条件如下:
- 用三等判断 props 是否发生变化
- 检查 context 是否发生变化
- 检查新旧 Fiber type 是否发生变化
如果判断条件为 true 则会给 didReceiveUpdate 变量赋值为 true,进入正式 beginWork 阶段
var oldProps = current.memoizedProps;
var newProps = workInProgress.pendingProps;
if (
// 对比新旧 props
oldProps !== newProps ||
// 检查 context 是否发生变化
hasContextChanged() ||
// 判断 Fiber type 是否发生变化
(workInProgress.type !== current.type )
) {
didReceiveUpdate = true;
} else {
......
}
如果判断条件为 false,会进入后续判断逻辑
此时会先调用 checkScheduledUpdateOrContext 函数检查 current 是否存在优先级相关的更新,关于 React 优先级相关我们先暂且按表不谈,进入 if 判断
- 不存在优先级相关的更新且 workInProgress 节点不存在 DidCapture flag
- True:跳过后续的正式 beginWork 阶段,进入 baliout 也就是组件复用逻辑
- False:判断当前 current 节点是否存在 ForceUpdateForLegacySuspense flag
- True:didReceiveUpdate 赋值为 true
- False:didReceiveUpdate 赋值为 false
// props 和 context 都没有发生变化,检查优先级相关
var hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(current, renderLanes);
if (
// 不存在优先级相关的更新c
!hasScheduledUpdateOrContext &&
// workInProgress 节点上不存在 DidCapture flag
(workInProgress.flags & DidCapture) === NoFlags
) {
didReceiveUpdate = false;
// 这里会跳过正式 beginWork 阶段,进入 baliout 逻辑也就是组件复用
return attemptEarlyBailoutIfNoScheduledUpdate(
current,
workInProgress,
renderLanes
);
}
// current 节点不存在 ForceUpdateForLegacySuspense flag
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
didReceiveUpdate = true;
} else {
didReceiveUpdate = false;
}
current 为空
首先 didReceiveUpdate 会被赋值为 false,紧接着会进入一个似乎关于 SSR 服务端渲染的判断逻辑,代码内容如下,这一段代码的行为还不清楚,暂时先跳过
didReceiveUpdate = false;
// 检查 hydrate 状态 和 是否存在 Forked flag
if (getIsHydrating() && isForkedChild(workInProgress)) {
// 后续的逻辑似乎和 SSR 服务端渲染有关
// 根据官方的注释,这边的代码似乎是为了给 React 的并发模式铺路
var slotIndex = workInProgress.index;
var numberOfForks = getForksAtLevel();
pushTreeId(workInProgress, numberOfForks, slotIndex);
}
至此整个前 beginWork 阶段就结束了,我们可以在这里做个小小的总结:在前beginWork 阶段主要是判断当前组件是否发生变化需要更新, 是否可以复用,在满足复用条件情况下会跳过 正式beginWork 阶段进入 baliout 逻辑而不再创建新的 Fiber 节点,从中可以看到对于特殊组件如 Suspense 而言可能并不会进入 baliout 逻辑
在此之外我们也可以看到一些很有意思的点:对于组件新旧 props 对比使用的是简单的三等判断
正式 beginWork 阶段
正式 beginWork 阶段开始,会将 workInProgress 的 lanes 清空,接着会进入一个 switch 逻辑,根据 tag 不同进入对应的 case 处理逻辑,代码如下:
workInProgress.lanes = NoLanes;
switch (workInProgress.tag) {
case IndeterminateComponent: ...
case LazyComponent: ...
// Function Component 处理逻辑
case FunctionComponent: ...
// Class Component 处理逻辑
case ClassComponent: ...
case HostRoot: ...
case HostComponent: ...
case HostText: ...
// Suspense 处理逻辑
case SuspenseComponent: ...
case HostPortal: ...
case ForwardRef: ...
case Fragment: ...
case Mode: ...
case Profiler: ...
case ContextProvider: ...
case ContextConsumer: ...
case MemoComponent: ...
case SimpleMemoComponent: ...
case IncompleteClassComponent: ...
case SuspenseListComponent: ...
case ScopeComponent: ...
case OffscreenComponent: ...
case LegacyHiddenComponent: ...
case CacheComponent: ...
}
switch 中的 case 逻辑太多了,全部都写出来会让笔记显得特别繁杂,这里只写出几个常用的处理逻辑如 HostComponent、FunctionComponent,其他处理逻辑后边有时间再另起一篇文章阐述
updateHostRoot
首先会执行 pushHostRootContext 函数,这个函数与 context 有关,暂且不谈
然后会接着执行 cloneUpdateQueue 方法
cloneUpdateQueue
这个方法比较简单,会判断 current 和 workInProgress 中的 updateQueue 是否相同,如果相同会创建新的对象重复赋值以清除引用,这里是为了保证后续对 workInProgress 的操作不会影响到 current
function cloneUpdateQueue(current, workInProgress) {
var queue = workInProgress.updateQueue;
var currentQueue = current.updateQueue;
// 用三等对比更新队列,如果为 true 表示 queue 还未清除引用
if (queue === currentQueue) {
// 清除引用
var clone = {
baseState: currentQueue.baseState,
firstBaseUpdate: currentQueue.firstBaseUpdate,
lastBaseUpdate: currentQueue.lastBaseUpdate,
shared: currentQueue.shared,
effects: currentQueue.effects
};
// 赋值
workInProgress.updateQueue = clone;
}
}
从 cloneUpdateQueue 函数出来回到 updateHostRoot 调用栈,会紧接着执行 processUpdateQueue 方法,这个方法和更新队列有关,暂时不展开讲
function updateHostRoot(current, workInProgress, renderLanes) {
// context 相关
pushHostRootContext(workInProgress);
// 取出 updateQueue
var updateQueue = workInProgress.updateQueue;
if (current === null || updateQueue === null) {
throw new Error("...");
}
// 取出组件新 props
var nextProps = workInProgress.pendingProps;
// 取出组件 state
var prevState = workInProgress.memoizedState;
// 从 state 中取出 element
var prevChildren = prevState.element;
cloneUpdateQueue(current, workInProgress);
processUpdateQueue(workInProgress, nextProps, null, renderLanes);
var nextState = workInProgress.memoizedState;
var root = workInProgress.stateNode;
{
var nextCache = nextState.cache;
pushRootCachePool(root);
pushCacheProvider(workInProgress, nextCache);
if (nextCache !== prevState.cache) {
propagateContextChange(
workInProgress,
CacheContext,
renderLanes
);
}
}
......
}
再往后是一些针对服务端渲染的一些处理逻辑,服务端渲染也不是这次讨论的目的,也先跳过 最终调用 reconcileChildren 为 FIber 创建一个子 Fiber 节点并返回
至此一个节点的 beginWork 流程就走完了,下一次会根据是否存在子 Fiber 节点判断是执行当前 WorkInProgress Fiber 节点的 completeWork ,还是继续对子节点执行 beginWork
beginWork
第一次触发更新,进入这个函数的逻辑前边和首屏渲染是都是一样的,但是在判断组件是否发生修改的时候逻辑和首屏渲染并不同,在首屏渲染,beginWork 会走到 switch 逻辑,并根据 Fiber tag 不同分别执行不同的 mount l逻辑,在这里会经过一些判断最终进入 attemptEarlyBailoutIfNoScheduledUpdate 这个函数,这个函数内部又有着一个 switch 逻辑,也是根据 workInProgress tag 属性分别进入不同的 case,在这个例子中进入的是 HostRoot 对应的 case,首先会执行 pushHostRootContext 函数,Context 相关 不谈,最后继续处理服务端渲染的逻辑最后返回交由 bailoutOnAlreadyFinishedWork 函数处理,bailoutOnAlreadyFinishedWork 会对 WorkInProgress 赋上 current 的 dependencies 属性,然后就是 bailoutOnAlreadyFinishedWork 最终的目的: 调用 cloneChildFibers
cloneChildFibers
这个函数从名字上就能看出来,目的是为了 clone Fiber 的 child 属性,这个函数会调用 createWorkInProgress 函数,从上边可以知道,这个函数可以根据传入的 current 和 WorkInProgress 进行判断是否创建一个新的 Fiber 节点或者复用已有的 current Fiber 节点,并将其互相链接,最终将结果返回,cloneChildFiber 接收到返回的 Fiber 子节点,将其链接到当前 WorkInProgress Fiber 的 child 属性上,并把 Fiber 子节点的 return 赋值 WorkInProgress Fiber
也就是说,如果在 beginWork 中当前,如果组件并没有发生变化且没有要执行的逻辑,那么他会直接 clone 已有的 Fiber 节点,而不会走到 reconcileChildren 函数创建新的 Fiber 节点
最后返回到 bailoutOnAlreadyFinishedWork 在由它返回到 beginWork,beginWork 流程执行结束