书籍传送门:React技术揭秘——卡颂

前言:整篇文章,我都将以问题+笔记的形式作为精读的导语展开叙述~文末有彩蛋喔~~~

上一篇文章中,我们已经了解了架构篇Render阶段,这一节我们继续上一节,聊一下Commit阶段。

Commit阶段主要分为before mutationmutationlayout三个阶段。

commitRoot(root);

上一章聊到commitRootcommit工作的起点

下面就正式介绍 commit阶段的流程:

before mutation
- DOM操作前

mutation
- DOM操作中

layout
- DOM操作完毕


在介绍before mutation之前,先回忆一件事:

V16之后,给部分componentWillxxx打上了UNSAFE_标记,这个 xxx分别指的是:componentWillMount,componentWillRecievePropscomponentWillUpdate.

为什么要加UNSAFE_呢?

  • 因为在concurrent模式下,render阶段会被多次打断重新开始,所以就会导致这些在render阶段执行的生命周期钩子,重复执行,导致数据不准或者死循环。

其实,大家也有注意到,出现的新的getDerivedStateFromProps就是替换componentWillRecieveProps的,而getSnapshotBeforeUpdate则是替换componentWillUpdate.

至于更加详细的差别,可以参考卡老师的:深入源码剖析componentWillXXX为什么UNSAFE : https://juejin.cn/post/6847902224287285255#comment

好了,现在开始进入这个阶段在做的三个步骤介绍:

  • 处理 DOM 节点渲染/删除后的 autoFocusblur 逻辑。

  • 调用 getSnapshotBeforeUpdate 生命周期钩子。

  • 调度 useEffect

我们着重关注下 useEffect,这里 useEffect 是异步的。

整个 useEffect 异步调用分为三步:

  • before mutation 阶段在 scheduleCallback 中调度 flushPassiveEffects
  • layout 阶段之后将 effectList 赋值给 rootWithPendingPassiveEffects
  • scheduleCallback 触发 flushPassiveEffectsflushPassiveEffects 内部遍历 rootWithPendingPassiveEffects

为什么 useEffect 是异步的呢?

componentDidMountcomponentDidUpdate 不同的是,在浏览器完成布局与绘制之后,传给 useEffect 的函数会延迟调用。这使得它适用于许多常见的副作用场景,比如设置订阅和事件处理等情况,因此不应在函数中执行阻塞浏览器更新屏幕的操作。

useEffect 异步执行的原因主要是防止同步执行时阻塞浏览器渲染。

!!!预留两个疑问,下篇文章中统一解答,同样也是我自己的疑问:

  1. 一个组件中多个 useEffect 的执行顺序是固定的吗?

  2. 一个组件中如果出现多个 useEffectuseLayoutEffect,执行顺序是怎样的?

  3. 嵌套组件中的 useEffectuseLayoutEffect 的执行顺序又是怎样的?


接下来,我们开始介绍 mutation阶段:

类似 before mutation 阶段,mutation 阶段也是遍历 effectList,执行函数。这里执行的是 commitMutationEffects

  • 根据 ContentReset effectTag 重置文字节点
  • 更新 ref
  • 根据 effectTag 分别处理,其中 effectTag 包括 (Placement | Update | Deletion | Hydrating)

Placement effect

Fiber 节点对应的 DOM 节点需要插入到页面中

  • 获取父级 DOM 节点。其中 finishedWork 为传入的 Fiber 节点。
  • 获取 Fiber 节点的 DOM 兄弟节点
  • 根据 DOM 兄弟节点是否存在决定调用 parentNode.insertBeforeparentNode.appendChild 执行 DOM 插入操作。

值得注意的一点是:Fiber 结构并不会和 DOM 结构完全一致,所以递归 Fiber 去找 DOM 结构时,复杂度会提升,且是指数倍。

Update effect

  • 区分 Host ComponentFunction Component

    • Host Component 中主要做了几件事
      • 处理 style
      • 处理 DANGEROUSLY_SET_INNER_HTML
      • 处理 children
      • 处理剩余 props (通常指的是原生 dom 节点的一些属性值 )
    • Function Component 中主要做了一件事
      • 调用 useLayoutEffect 的销毁函数

Deletion effect

  • 递归调用 Fiber 节点及其子孙 Fiber 节点中 fiber.tagClassComponentcomponentWillUnmount (opens new window) 生命周期钩子,从页面移除 Fiber 节点对应 DOM 节点
  • 解绑 ref
  • 调度 useEffect 的销毁函数

文末彩蛋:手写笔记