1
0
Fork 0
Obsidian 管理的个人笔记仓库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

250 lines
10 KiB

>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 流程执行结束