前言
本文主要内容是首次挂载时的completeWork
。其实整个completeWork
过程是最简单的,之所以不将更新时的也一起讲了,是因为不想将dom-diff部分的代码也融合进来,保持completeWork
这部分代码的纯净,更好理解这个阶段都做了些什么。不过后面的流程图会有一个大概的更新时的completeWork
的部分。
承接上文,看过我上一篇beginWork
文章中的流程图,可以知道completeWork
执行时机在这(如图)。我们现在就来完善这部分。
completeWork
一、做了什么
completeWork
阶段主要就是根据fiber树,生成对应的真实dom树,每一个原生fiber节点上面(也就是fiber.tag为5或6的)都有一个stateNode
属性,这个属性的内容就是自己的真实dom。
二、怎么做
completeWork主流程
图中的workInProgress是一个全局变量,它就是当前fiber的替身,如果当前fiber的替身是null,那就创建一个新fiber替换掉这个null。因为本文是承接上文,就不过多解释这个workInProgress。
上图中,无论是创建真实dom还是将儿子的真实dom都挂载到自己的真实dom上都好理解,唯一可能有疑惑的点就是他们的结束节点:更新所有子节点上的副作用之和到自己的subtreeFlags上(下图这个位置)。
这里就要说说fiber上的这两个属性的作用了。
flags
:标识自己身上是否有副作用。如果有,在commitRoot阶段会执行对应的副作用的操作。subtreeFlags
:标识自己的子节点是否有副作用。如果有,在commitRoot阶段会开始遍历自己的子节点,执行所有有副作用的子节点的对应的副作用的操作。
因为在commitRoot阶段,我们能获取到的信息是根fiber,它底下是否有副作用要执行,就要通过底下的节点们不断收集自己的subtrssFlags
,最后反馈到根fiber上。我们来看一个示例。
// 示例的结构
<h1><span>hello world</span></h1>
看绿色框部分,每一次自己的subtreeFlags都为子节点的subtreeFlags + 子节点的flags,以此方式一直累加到root上。这样在commitRoot阶段,根fiber看到自己的subtreeFlags有副作用就知道要遍历自己的子节点去执行他们的副作用。同样的,如果根fiber看到自己的subtreeFlags没有副作用就不需要管子节点了。说到底,这里属于一个优化,如果没有subtreeFlags,每次commitRoot就都需要进行子节点的遍历。
到此,首次挂载时的completeWork就结束了,内容很少,也很好理解,有兴趣的读者,可以简单调试下文末贴的代码。
三、结果
结合上上文的beginWork,我们来看看整个流程。
// 结构
<div>
react:
<span>hello world</span>
</div>
输出的每行的内容为:阶段 fiber.tag 内容/节点类型
依然是一个深度优先的逻辑,先完成最底下的子节点的completeWork(文本节点: react
:和span
节点),然后是上一层的div
节点,最后是根
节点。
结尾
这里是只有到completeWork阶段代码的仓库分支,大家可亲自用这代码进行调试,过一遍整个completeWork流程。
ps:这部分的代码,函数名大多是基本与源码一致,若觉得笔者有错误的地方或想拓展哪部分,可通过函数名直接在源码中搜索。