>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 为空 didReceiveUpdate 赋值为 false ## 正式 beginWork 阶段 ### beginWork >beginWork 执行在递归节点的 Fiber 创建之前,主要是为传入的 Fiber 节点根据类型创建第一个子 Fiber 节点 首先会对 current 做空值判断,因为对于首屏渲染而言,当前传入的 current Fiber 节点是 FiberNode,所以是存在的,会进入 current !== null 的逻辑,然后会判断当前 Fiber 节点是否发生变化,然后赋值 `didReceiveUpdate` 做更新标识,判断的条件如下: 1. 新旧 props 是否相同 2. Context 是否发生变化 3. Fiber 节点 type 是否发生变化 在本次首屏渲染中,以上的条件都为否,会进入后续条件判断,在后续的条件判断中主要是为了应付一些特殊的场景,例如错误边界和 legacy 模式,可以看到官方留下的代码注释,在我们现在的场景下,最终会走到 didReceiveUpdate 为 false 的情况下,也就是当前 Fiber 并没有变化,因为在之前的 createWorkInProgress 中 WorkFiberProgress 被赋值上了 current Fiber 的同名属性,所以它们会是相同的两个 FIber 节点,在这里的代码可以看到:beginWork 中还有对优先值 lanes 的一些处理,在这里先不展开,后续再补充 最后会走入 switch 环节,这了主要根据传入的 WorkInProgress Fiber 节点的类型进入不同的处理逻辑,这也是 beginWork 最主要的任务:为当前 WorkInProgress Fiber 节点创建它的第一个 Fiber 子节点,其内部根据 Fiber 的 tag 不同有对应的: - updateClassComponent 处理 Class Component - updateHostComponent 处理 Host Component - updateSuspenseComponent 处理 Suspense Component 本次的是首屏渲染,传入的 WorkInProgress Fiber 为 FiberNode,所以会进入 HostRoot 的 case,执行 updateHostRoot 方法 ### updateHostRoot updateHostRoot 函数中首先会执行 pushHostRootContext 方法,这个方法和 Context 相关,现在暂且不谈 在 updateHostRoot 逻辑一开始,它会将 current 和 WorkInProgress 传递给 cloneUpdateQueue 这个函数,这个函数会将 Fiber 中的 queue 属性进行 clone,除非它们已经是 clone 之后的属性,那么具体是怎么做的判断是否是 clone 过的属性呢?其实很简单,它只是使用了 === 三个等于判断属性的引用是否相同,也就是说这个函数实际上做的是清除引用,保证在之后对 WorkInProgress 的操作不会影响到 current Fiber 树,判断两个 Fiber 树的 updateQueue 属性的引用完全相同,那么就会创建一个新的对象并重复赋值达到清除应用的目的,最后赋值给 WorkInProgress Fiber 节点 之后,再把 WorkInProgress Fiber 节点交给 processUpdateQueue 函数,这个函数主要是处理更新队列,在这里先不展开,TODO 再往后是一些针对服务端渲染的一些处理逻辑,服务端渲染也不是这次讨论的目的,也先跳过 最终调用 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 流程执行结束