|
|
|
>beginWork 执行在递归节点的 Fiber 创建之前,主要是为传入的 Fiber 节点根据类型创建第一个子 Fiber 节点
|
|
|
|
|
|
|
|
[代码位置](https://github.com/facebook/react/blob/bd4784c8f8c6b17cf45c712db8ed8ed19a622b26/packages/react-reconciler/src/ReactFiberBeginWork.old.js#L3818)
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
function beginWork(current, workInProgress, renderLanes) {
|
|
|
|
|
|
|
|
// 前 beginWork 阶段
|
|
|
|
if (current !== null) {
|
|
|
|
......
|
|
|
|
} else {
|
|
|
|
......
|
|
|
|
}
|
|
|
|
|
|
|
|
// 正式 beginWork 阶段
|
|
|
|
switch (workInProgress.tag) {
|
|
|
|
......
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
beginWork 函数接受三个参数,分别是 current 节点,workInProgress 节点和 renderLanes 优先级,根据 beginWork 函数的结构,我们可以分成两个阶段,分别是:前 beginWork 阶段和正式 beginWork 阶段
|
|
|
|
|
|
|
|
## 前 beginWork 阶段
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
if (current !== null) {
|
|
|
|
......
|
|
|
|
} else {
|
|
|
|
didReceiveUpdate = false;
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
在进入正式 beginWork 阶段之前,会先对传入的 current 节点进行空值判断,根据 current 是否为空进入不同的处理逻辑。
|
|
|
|
|
|
|
|
**那么为什么需要判断 current 是否为空呢?答案是为了性能,前面说到 beginWork 函数的主要任务就是给当前传入的 Fiber 节点创建它的第一个子 Fiber 节点,要是在上次更新和本次更新中当前的 Fiber 节点并没有发生变化,那么还需要再次创建一个新的 Fiber 节点吗?肯定是不需要的,所以我们只需要将这个已经存在的并没有发生变化的 Fiber 节点拿过来复用就行了,而这段逻辑正是在前 beginWork 阶段中判断并执行的**
|
|
|
|
这一段代码的主要目的是为了赋值 didReceiveUpdate 变量,这个变量表示在本次更新中当前 current 节点是否存在变化
|
|
|
|
|
|
|
|
### current 不为空
|
|
|
|
|
|
|
|
在 current 不为空的逻辑中,会先取出先前存储在 Fiber 节点中的新旧 props,连同其他几个判断条件一起做 if 判断,判断条件如下:
|
|
|
|
1. 用三等判断 props 是否发生变化
|
|
|
|
2. 检查 context 是否发生变化
|
|
|
|
3. 检查新旧 Fiber type 是否发生变化
|
|
|
|
|
|
|
|
如果判断条件为 true 则会给 didReceiveUpdate 变量赋值为 true,进入正式 beginWork 阶段
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
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
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
// 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 服务端渲染的判断逻辑,代码内容如下,这一段代码的行为还不清楚,暂时先跳过
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
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 处理逻辑,代码如下:
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
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
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
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 方法,这个方法和更新队列有关,暂时不展开讲
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
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 流程执行结束
|