一篇带你理解 React 的 Commit 阶段

网站建设2年前发布
66 00

一篇带你理解 React 的 Commit 阶段,大家好,我是前端西瓜哥。今天我们来详细讲解一下 React 的 commit阶段的逻辑。,commitRootImpl 中的三个函数的调用分别对应这个三个阶段:,在 reconcil (调和)阶段,给 fiber 打了很多的 flags(标记),commit 阶段是会读取这些 flags 进行不同的操作的。,flags 是通过二进制掩码的方式来保存的,掩码优点是节省内存,缺点是可读性很差。,使用或位运算,可以将多个 flag 组合成一个组。,我这三个阶段 用到的组合掩码 为:,BeforeMutation 阶段。,commitRootImpl 首先会 调用 commitBeforeMutationEffects 方法。,commitBeforeMutationEffects 的核心实现:,主要是调用这个 commitBeforeMutationEffects_begin 方法。,begin 干了啥?,进行深度优先遍历,找到最后一个带有 BeforeMutation 标识的 fiber。这是因为 useEffect 的调用逻辑是从子到父,要找最后一个 fiber 作为起点。,commitBeforeMutationEffects_begin 的核心实现:,subtreeFlags 是当前 fiber 的子树的标识汇总,目的是防止无意义的完整深度遍历,能够更早地结束遍历。如果直接用 flags,是要遍历到叶子节点才能知道到底谁是要找的最有一个节点。,找到后,调用 complete 。,commitBeforeMutationEffects_complete 实现为:,前面很多逻辑都是遍历的逻辑,真正的核心操作在 commitBeforeMutationEffectsOnFiber 方法。,做了什么?,对标记了 Snapshot 的组件进行处理,通常是类组件,会 调用类组件实例 instance 的 getSnapshotBeforeUpdate 方法,生成快照对象,然后再放到 instance.__reactInternalSnapshotBeforeUpdate 下,作为之后的 componentDidUpdate 钩子函数的第三个参数。,其他类型的组件基本啥都不做。,mutation 阶段是最重要的阶段,在这个阶段,React 真正地更新了文档 DOM 树。,入口函数是 commitMutationEffects,但它只是 commitMutationEffectsOnFiber 的封装。,每个 fiber 都要传入 commitMutationEffectsOnFiber,执行 mutation 主逻辑。,从这调用栈可知 commitMutationEffectsOnFiber 递归调用了多次,形成了很长的调用栈。,一篇带你理解 React 的 Commit 阶段,commitMutationEffectsOnFiber 的核心实现为:,对不同类型的 fiber 会进行不同的处理,但有一些公共逻辑会执行的,那就是:,首先是调用 recursivelyTraverseMutationEffects 方法,这个方法会执行删除逻辑。,该方法会读取 fiber 的 deletions 数组,对这些要删除的 fiber 进行操作。,对于要删除的 fiber,我们这里讨论原生组件、类组件、函数组件这 3 种组件类型 fiber 的删除逻辑。,对于原生组件类型(div、span 这些):,对于类组件:,对于函数组件:,完成删除逻辑后,接着就是调用 commitReconciliationEffects,这个方法负责往真实 DOM 树中插入 DOM 节点。,commitReconciliationEffects 核心内容:,如果 finishedWork 有 Placement 标识,则调用 commitPlacement 方法。,commitPlacement 的逻辑为:,commitPlacement 实现如下。,Placement 只会在原生组件和 fiber 根节点上标记,没有函数组件和类组件什么事。,对于可复用的原生组件,会 调用 commitUpdate 进行更新。,commitUpdate 的代码:,​类组件不会进行更新操作。,对于函数组件,会依次调用:,需要注意,函数组件初次挂载,flags 也会标记为 Update,走更新逻辑。这也是为什么 useEffect 在函数组件挂载时也会执行,和类组件的 componentDidUpate 不同。,最后是 Layout 阶段。,commitLayoutEffects 的实现:,和 BeforeMutation 阶段一样,先深度优先递归,找最后一个有 LayoutMask 标记的 fiber。,然后从下往上调用 complete 逻辑,确保逻辑是从底部到顶部,即先子后父。,complete 代码:,核心工作在这个 commitLayoutEffectOnFiber 方法。它会根据组件类型不同执行不同逻辑。,对于函数组件,会调用 useLayoutEffect 的回调函数(effect.create)。,对于类组件:,处理完后,接下来就会 更新 ref :,操作很简单,对于原生组件,就是给 fiber.ref.current 赋值为 fiber.stateNode。,现在还差 useEffect 没调用了。,useEffect 不在同步的 commit 阶段中执行。它是异步的,被 scheduler 异步调度执行。,先执行所有 useEffect 的 destroy 方法,然后才执行所有 useEffect 的 create 方法。并保持顺序是先子后父。,画个流程图:,一篇带你理解 React 的 Commit 阶段,create 表示传给 useEffect 的回调函数,destroy 为调用该回调函数返回的销毁函数。,总结一下。,commit 分成三个阶段:BeforeMuation、Muation 以及 Layout 阶段。,最后是 commit 阶段外的 useEffect,它被 Scheduler 异步调度执行,先执行完整棵树的 destroy,再执行完整棵树的 create。

© 版权声明

相关文章