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