React源码系列(三)------ commitRoot
前言
本文主要讲commitRoot阶段,该阶段结束算是迎来了一波小胜利,因为该阶段提交了副作用之后,终于能在页面中看到点效果了。
commitRoot
一、做了什么
commirRoot阶段主要就是根据fiber树构建出整个dom树,然后直接将他们提交到浏览器,让浏览器渲染出它们。
二、怎么做
主流程
其实整个commitRoot非常简单,就是递归地完成前面两阶段标识的插入和更新操作,然后再替换掉当前正被展示的div#root(双缓冲原理,详情可看第一篇)。
这里我们来讲讲它是如何标识和如何识别标识以及完成了之后如何抹去标识的。
插入和更新标识
所谓的标识其实就是二进制,react事先做好一个字典,二进制的哪一个bit对应哪一种操作。直接看以下例子。
// 这是预设好的字典
export const NoFlags = 0b000000000000000000000000000; // 无副作用
export const Placement = 0b000000000000000000000000010; // 插入操作
export const Update = 0b000000000000000000000000100; // 更新操作
export const MutationMask = Placement | Update; // 标识有更新或插入操作
每一个fiber最开始都是NoFlags。当需要标识插入或更新操作时,直接或或或上他们就行,而完成操作后需要去抹去它们只需要与与与上一个取反的它们就行。
-
与(&):同一个bit上,有0则为0,都为1才为1。
-
或(|):同一个bit上,有1则为1,都为0才为0。
-
取反(~):原先为1的变0,为0的变1。
// 比如要标识插入操作
fiber.flags |= Placement;
// 此时fiber的flags就是0b000000000000000000000000010;
// 再标识上更新操作
fiber.flags |= Update;
// 此时fiber的flags就是0b000000000000000000000000110;
// 完成插入操作后,抹去插入操作
fiber.flags &= ~Placement;
// 此时fiber.flags就是0b000000000000000000000000100;
// 完成更新操作后,抹去更新操作
fiber.flags &= ~Upadte;
// 此时fiber.flags就是0b000000000000000000000000000;
难点
该阶段调试代码唯一难点就是主流图中的getHostSibling(fiber)这个方法。由于此时是在完成插入操作,所谓插入,就是要先找到插入点,这个方法就是在找正确的插入点。共两种情况。
情况1
情况1:插入点在两个已经存在的child中间。
当我们进行到child2的插入操作时,调用getHostSibling(fiber)方法,它会遍历child2之后的child,看看他们谁是已经存在的child(child4),最后发现child4就是那个已经存在的child,那我们直接在child4前进行插入。
同理,执行到child3的插入操作时,也会找到child4,然后在child4前面进行插入。
情况2
情况2:插入点之后无已存在的child。
当我们进行到child3的插入操作时,调用getHostSibling(fiber)方法,它会遍历child3之后的child,发现他们都是需要新插入的child,而非已经存在的child,那我们就直接将child3 append到father后面就行。
当我们进行到child4的插入操作时,后面已经没有child了,直接append就行。
完整的getHostSibling(fiber)流程
三、结果
整个commitRoot结束后,页面就会展现出东西了,直接看图。
// 此时的dom结构
<div>react: <span style={{ color: 'skyblue' }}>hello world</span></div>
结尾
这次的代码中会有一个冷门语法,叫标签语法label,一般label搭配break和continue语句使用,主要作用是在嵌套的循环或者判断中,可以指定跳出或继续的循环,看下面一个小例子。
outer: while(true) {
console.log(1);
while(true) {
console.log(2);
continue outer;
}
}
// 121212121212121212
// 不使用label
while(true) {
console.log(1);
while(true) {
console.log(2);
continue;
}
}
// 122222222222222222
这里是只有到commitRoot阶段代码的仓库分支,大家可亲自用这代码进行调试,过一遍整个commitRoot流程。
ps:这部分的代码,函数名大多是基本与源码一致,若觉得笔者有错误的地方或想拓展哪部分,可通过函数名直接在源码中搜索。
转载自:https://juejin.cn/post/7232460999189987389