- 每一个 Fiber 节点都会链接它的第一个子节点、下一个兄弟节点和父节点,并且在 Fiber 节点中还会保存组件的状态和需要更新的副作用 - Reconciler 会从根节点一直向下递到子节点,再从子节点一路向上归到根节点 - React 的双缓存:React 有着 current Fiber 树和存在于内存中的 workInProgress Fiber 树 - React 的双缓存:每一次 React 的更新都会创建一个 workInProgress Fiber 树,current Fiber 和 workInProgress Fiber 之间使用 alternate 属性链接,方便公用属性,当 workInProgress Fiber 完成渲染,FiberRootNode 的指针就会指向 workInProgress Fiber 树的根节点 RootFiber,这时 workInProgress Fiber 就变成了 current Fiber 树 - React 会尽量的复用 Fiber,在创建 workInProgress Fiber 时,如果 current Fiber 中节点的 alternate 属性已经指向一个 Fiber 节点,那么新创建的 workInProgress Fiber 节点就会基于这个 alternate 指向的 Fiber 节点来创建,这种基于已有 Fiber 节点做对比生成新的 workInProgress Fiber 的过程就是 diff 算法,所以首屏渲染和更新渲染最大的区别就在于是否有 diff 算法 - 对于首屏渲染来说,只有根节点具有 current Fiber 而子节点不存在 - React render 阶段的工作分为归阶段和递阶段,在递阶段执行 beginWork,而在归阶段,执行 completeWork - 如果 render 的递归阶段,如果节点只存在一个文本子节点,那么这个子节点将不会进入 beginWork 递阶段,而是由它父节点直接进入 completeWork 归阶段,这是 React 做的一个优化,而这一个逻辑在 updateHostComponent方法中的 isDirectTextChild 可以看到 - Host Components 会进入 updateHostComponent 逻辑,在 React 18 是 updateHostComponent$1 - updateHostComponent 方法中的 reconcileChildren 方法会为当前 Fiber 节点创建它的子 Fiber 节点,也就是 Fiber 中的 child 属性 - reconcileChildren 方法接受 current 参数,通过判断这个参数是否为 null,分别执行 mountChildFibers 或 reconcileChildFibers 方法 - mountChildFibers 和 reconcileChildFibers 都是由 ChildReconciler 方法创建的,只是传入的布尔值会不同,而这个参数表示是否追踪副作用,mountChildFibers 为 false,reconcileChildFibers则相反 - 以 reconcileChildFibers 为例,会对 Children 的类型做判断,对判断结果分别做相应操作 - completeWork 会对 beginWork 创建好的 Fiber 进行填充,根据 Fiber 类型的不同有不同的处理逻辑,其中有一步就是创建真实 DOM 元素并将之前创建好的 DOM 元素插入 - finalizeInitialChildren 为创建的 DOM 元素,插入已有的 props,内部也根据 Fiber 节点的 tag 区分不同的处理逻辑,还有对 props 是否合法的校验,甚至根据 props 的属性也做了不同逻辑的处理,最终交由 setValueForProperty 处理 - completeWork 中的 appendAllChildren 会将创建好的真实DOM元素插入之前创建的子DOM元素 - 对于首屏渲染,只会有一个节点被打上 effectTag,就是根节点,只需要根节点被打上 effectTag 那么就能渲染剩下的全部内容 - createWorkInProgress 会创建新的 WorkInProgress Fiber,会根据 current Fiber 的 alternate 属性检查是否已经存在 WorkInProgress Fiber,如果没有会通过 createFiber 创建新的 Fiber 并将 current Fiber 的已有属性进行赋值,最后让 WorkInProgress Fiber 的 alternate 属性进行双向链接 - createWorkInProgress 之后就开始 Fiber 的递归创建,也就是 beginWork 和 completeWork,可以小小的归结,beginWork 是在 Fiber 递的过程中不断地创建下一个子 Fiber 节点,而 completeWork 则是填充创建好的 Fiber - createWorkInProgress 的逻辑中,如果已经 alternate 存在,那么就会复用 Fiber,然后赋值同名参数,里边还会赋值 children - createWorkInProgress -> beginWork -> completeWork - 首屏渲染时,会创建 Fiber 树的第一个节点 FiberNode,这个时候创建的 Fiber 树会被 FiberRootNode 的 current 指针指向,之后进入 createWorkInProgress 函数,会从 current 树的 alternate 属性中获取 workInProgress 的 FiberNode,由于首屏渲染,这个时候只存在一棵树那就是 current Fiber,这时就会创建有个新的 Fiber 节点作为 workInProgress 的 FiberNode,并赋值 current FiberNode 的同名属性,最后交由 beginWork 开始创建子 Fiber 节点,直到 Fiber 树完成生成,提交给 commit 阶段渲染到页面上,当第一次触发页面刷新,也会进入 createWorkInProgress,这是存在一个完备的 Fiber 树,也就是首屏渲染时生成的 workInProgress,但是在第一次页面更新,它已经是此次的 current Fiber 树,与首屏渲染时的 createWorkInProgress 不同,这次的 current Fiber 树的 FiberNode 已经存在,那么就不会创建新的 Fiber 节点,而是复用已有的 FiberNode,并赋值上同名属性,最后再交由 beginWork生成子 Fiber 节点,最后逻辑依旧,完成 FIber 树的生成之后 commit 渲染出页面,直到第二次渲染,这时两棵树 current Fiber 树和 WorkInProgress 树都已经存在 - beginWork 在页面更新时,会根据一些条件判断 didReceiveUpdate 的 true 或者 false,这个变量代表了,在本次更新中这个 Fiber 节点是否有变化,这些条件分别是 1. 是否有新旧 props 2. context 是否发生变化 3. type 是否发生变化 - 如果条件都为否,那么 didReceiveUpdate 变成 false 之外还会判断本次更新当前 Fiber 是否存在需要执行的任务 - 如果也没有任务需要执行,和首屏渲染进入 update 的时候不同,最终会走到 bailoutOnAlreadyFinishedWork 函数中去,这个函数最终会执行 cloneChildFibers 方法,直接克隆一个子 Fiber 节点挂载到当前 Fiber 节点的 child 上 - createWorkInProgress 根据情况创建新的 Fiber 节点或者复用已有 Fiber 节点 - 对于 Function Component 会调用 renderWithHooks 方法,这个方法会执行 Function Component 自身,返回的值就是 React.createElement 返回的 JSX 对象,这里和 Host Component 不一样 - reconcileChildren 根据 current Fiber 树、 WorkInProgress Fiber 树和 JSX 对象来生成子 Fiber 节点 - WorkInProgress Fiber 节点不存在 alternate 有可能表示,在上一次更新中不存在这个 Fiber 节点 - 对于一个 Host Components 来说,如果它有属性的增删改,那么它的Fiber 的 updateQueue 属性会赋值对应的一个数组,这个数组是由两轮属性的循环生成的,属性第 i 项为属性名,第 i + 1 项为属性值,在这个数组存在的情况下,也就是属性有改变的情况下会进入 markUpdate 函数的逻辑,这个函数会为当前的 Fiber 节点打上 Update 的标记(effectTag) - 如果一个 Fiber 节点存在 effectTag,那么它会其他包含 effectTag 的 Fiber 节点链接形成一个链表,在 commit 阶段只需要遍历这个链表就能找出需要变动的 Fiber 节点 - 在 commit 节点遍历 effectList 的操作叫做 mutation - ClassComponent 的 getSnapshotBeforeUpdate 生命周期是在 before mutation 阶段被调用的 - useEffect 的回调函数会在 before mutation 阶段会以普通优先级被调度,然后在 commit 阶段执行完毕之后再异步执行 - commit 阶段开始于 commitRoot 函数,这个函数内部会执行 runWithPriority 函数,该函数接收两个参数,第一个是调度的优先级,第二个是调度的回调函数,在这个函数中触发的任务调度都会以第一个参数传递的优先级执行 - 如果一个 FunctionComponent 内部有需要执行的 useEffect ,那么这个 FunctionComponent 的 Fiber 节点就会被打上 PassiveEffect 的 effectTag - commitRootImpl 函数开头的fec do..while 循环是为了判断当前 Fiber 是否还有还未执行的 useEffect,如果有会再次执行 flushPassiveEft 函数 - commitRootImpl 内会处理一些离散事件,然后会重置在 render 阶段使用的一些全局变量,然后会处理包含 effectList 链表:因为 effectList 只会处理根节点之下的子节点,所以这里要判断根节点是否存在 effectTag 然后将其挂载到 effectList 的末尾 - commitBeforeMutationEffect、commitMutationEffects、commitLayoutEffects 三个函数分别代表了 mutation 的三个不同阶段 - commitRootImpl 中有许多与 Interaction 相关的逻辑,这些逻辑和 React 的性能追踪有关,会在 React DevTool 中使用 - 在 commit 阶段的结尾,也就是 commitRootImpl 函数的结尾,由于在 commit 阶段可能会产生新的更新,所以在这里会将整个应用的根节点重新调度一次 - React 会将一些同步的更新放在一个叫 SyncCallbackQueue 的队列中,每次执行 flushSyncCallbackQueue 函数就会执行整个队列中的同步任务,比如 useLayoutEffect 中触发的 setState