前言
前面我们做好了源码调试环境,学习了JSX转化为createElement的过程,现在我们接下来了解一下:
- React16所采用的架构,对一个架构的整体了解
- 从数据结构方面了解Fiber架构
- 双缓存技术的一个简单介绍
在继续阅读React16源码之前,我们通过今天的学习,我们对React16有个整体面的认知,了解React16的核心概念,看看人家是怎么通过技术解决对应需求问题的!!!
React架构
React16 版本的架构可以分为三层: 调度层,协调层,渲染层.
- Scheduler(调度层): 调度任务的优先级,高优任务可以优先进入协调器
- Reconciler(协调层): 构建Fiber数据结构,比对Fiber对象找出差异,记录Fiber对象要进行的DOM操作
- Renderer(渲染层): 负责将发生变化的部分渲染到页面上
Schedule 调度层
1. 面对问题
在React15当中,是没有调度层的。为什么要在React16当中加入调度层呢?因为在React15中,采用了循环加递归的方式进行VirtualDOM的比对,由于递归使用JavaScript自身执行栈。一旦开始就没有办法停止,只能等到任务执行完成。如果VirtualDOM树的层级比较深,VirtualDOM的比对就会长期占用JavaScript主线程,由于JavaScript又是单线程的无法同时执行其他任务,所以在对比的过程中无法响应用户操作,无法即时执行元素动画,造成了页面卡顿的现象。
2. 解决方案
在React16的版本中,放弃了JavaScript递归的方式进行VirtualDOM的比对,而是采用循环模拟递归。而且比对的过程是利用浏览器的空闲时间完成的,不会长期占用主线程,这就解决了virtualDOM比对造成页面卡顿的问题。
3. 参考方案
在window对象中提供了requestIdleCallback API,他可以浏览器的空闲时间执行任务,但是它自身也存在一些问题,比如说并不是所有浏览器都支持它,而且他的触发频率也不是很稳定,所以React最终放弃了requestIdleCallback的使用。
4. 实际解决方案
在React中,官方实现自己的任务调度库,这个库就叫做Scheduler。他也可以实现在浏览器空闲时执行任务,而且还可以设置任务的优先级,高优先级任务限制性,低优先级任务后执行。
Schduler 存储在 package/scheduler文件夹中

Reconciler 协调层
在React 15中,协调器和渲染器交替执行,即找到了差异就直接更新差异。在React16中,这种情况发生了变化,协调器和渲染器不再交替执行。协调器负责只找出差异,所有差异找出之后,统一交给渲染器进行DOM的更新。也就是说协调器的主要任务就是找出差异部分,并且为了差异打上标记。
Renderer 渲染层
渲染器根据协调器为Fiber节点打的标记,同步执行对应的DOM操作。
既然比对的过程从递归 变成了 可以中断的循环,那么React是如何解决中断更新时DOM渲染不完全的问题呢?
其实根本就不存在这个问题,因为在整个过程中,调度器和协调的工作是在内存中完成的是可以被打断的,渲染器的工作被设定成不可以被打断,所以不存在DOM不完全的问题。
Fiber数据结构
我们来看下,React16中的Fiber数据结构,Fiber其实就是普通的JavaScript对象,是由VirtualDOM对象演变而来的,在Fiber的对象当中有很多属性,我会介绍一些比较重要的对象,一起认识Fiber的数据结构。
虽然Fiber属性比较多,但是我们在阅读源码的时候会将一些属性进行归类。这样我们会更容易理解一些。
大体我们可以把他们分为四类:
- 和DOM实例相关
- tag, type, stateNode
- 和构建Fiber树相关
- return, child, sibling,alternate
- 和组件状态相关
- pendingProps, memoizedProps, memoizedState
- 和副作用相关
- UpdateQueue, EffectTag
和DOM实例相关
1. tag
用来区分组件的类型,当前的Fiber对象表现的是函数组件,还是类组件,还是普通的React元素,还是一些其他的组件类型呢?tag属性是用来区分它们的。
tag属性值我们可以去查看WorkTag,其实WorkTag的值为0到22,不同的数值表示不同的组件类型。
比如0表示函数组件,比如1表示类组件,比如3表示当前组件挂载点对应的Fiber对象,默认情况就是ID为root的那个div对应的Fiebr对象。5代表普通的React节点,比如div,span这样的节点。
还有其他的我们暂时忽略他,我们也记不住,也没有必要记住他们。在后面我们看源码遇到了回来查看WorkTag就可以了
WorkTag 文件位置src/react/packages/shared/ReactWorkTags.js

2. type
type这个在之前的学习中,我们已经很熟悉了,其实就是之前createdElement方法的第一个参数,表示节点的类型。如果当前节点是div,或者span,type属性中存储的值就是字符串类型的”div“或者”span“。如果当前元素是组件,那么type存储的就是组件的构造函数。
3. stateNode
这个属性我们也不陌生,在模拟React源码时我们就使用过,如果当前Fiber对象表示的是普通的DOM节点,stateNode存储的就是节点对应的真实DOM对象。如果当前Fiber对象表示的是类组件,stateNode存储的就是组件的实例对象。如果当前Fiber对象表示的函数组件,stateNode存储的就是null,因为函数组件没有实例。
和构建Fiber树相关
1. return, child, sibling
Fiber树和DOM树,可以说其实是对应的关系,但不是完全对应的。在DOM树中有子节点,父节点,兄弟节点这样的定义和关系,因为有这些才能构成树这种数据结构Fiber树也一样,也有这些概念,return表示的是父级Fiber节点,child表示是子级Fiber节点,sibling表示的是下一个兄弟Fiber节点。这样的话我们无论处于当前哪个节点,都可以通过该节点找到子级,父级以及同级的Fiber节点。
alternate
这个属性也是用于构建Fiber树相关的属性,这里先不说,下面还会介绍一个双缓存技术会重点提及alternate,更有助于理解的连贯性,这里暂时提一下,在下面会和双缓存技术重点解释。
和组件状态相关的属性
1. pendingProps
该属性存储的是组件即将更新的props,
2. memoizedProps
存储的是上一次组件更新后的前props,就是旧的props
3. memoizedState
存储的是上一次组件更新后的前state,就是旧的state
和副作用相关的属性
副作用值得就是可以出发DOM操作的属性
1. UpdateQueue
从属性命名可以看得出来,它表示的是任务队列,当前的Fiber对应的组件要执行的任务,比如组件的状态更新,再比如组件的初始化渲染,都属于任务的一种,都会存储在这个任务队列当中。
既然是任务队列,在队列当中就可以存储多个任务。在什么情况下会存在多个任务呢?比如在组件当中多次调用setState方法,进行状态的更新。在setState方法被调用后,更新并不是马上发生的,react会将多个更新任务放在这个队列当中,最后执行批量更新操作。updateQueue属性值其实就是JavaScript对象,updateQueue对象会以链表的方式存储一个一个需要更新的任务。具体是如何实现的我们在后面的源码学习中会了解到。
2. EffectTag
他表示的是当前对应的Fiber节点对应的DOM节点需要进行什么样的操作。他的值我们可以查看sideEffectTag(文件位置: src/react/packages/shared/ReactSideEffectTags.js)的值,它的值都是以二进制的方式进行存储的,但在代码运行的时候我们可以得到十进制的数值。
比如0 是NoEffect表示的是当前DOM不需要进行任何操作,比如1表示的是PerformedWork表示的是当前的DOM节点操作已经完成。当fiber节点对应的DOM操作执行完成以后EffectTag属性值会重置为1(PerfoormWork),再比如2(Placement)表示当前DOM节点要被插入到当前页面当中。再比如4(Update)表示当前节点需要被更新,再比如8(Deletion)表示当前DOM节点需要被删除。
export type SideEffectTag = number;
// Don't change these two values. They're used by React Dev Tools.
export const NoEffect = /* */ 0b0000000000000; // 0
export const PerformedWork = /* */ 0b0000000000001; // 1
// You can change the rest (and add more).
export const Placement = /* */ 0b0000000000010; // 2
export const Update = /* */ 0b0000000000100; // 4
export const PlacementAndUpdate = /* */ 0b0000000000110; // 6
export const Deletion = /* */ 0b0000000001000; // 8
export const ContentReset = /* */ 0b0000000010000; // 16
export const Callback = /* */ 0b0000000100000; // 32
export const DidCapture = /* */ 0b0000001000000; // 64
export const Ref = /* */ 0b0000010000000; // 128
export const Snapshot = /* */ 0b0000100000000; // 256
export const Passive = /* */ 0b0001000000000; // 512
export const Hydrating = /* */ 0b0010000000000; // 1024
export const HydratingAndUpdate = /* */ 0b0010000000100; // 1028
// Passive & Update & Callback & Ref & Snapshot
export const LifecycleEffectMask = /* */ 0b0001110100100; // 932
// Union of all host effects
export const HostEffectMask = /* */ 0b0011111111111; // 2047
export const Incomplete = /* */ 0b0100000000000; // 2048
export const ShouldCapture = /* */ 0b1000000000000; // 4096
复制代码
3. firstEffect,nextEffect,lastEffect
这些属性存储的是当前的fiber节点的子级fiber节点,是需要执行副作用的fiber节点,在前面我们模拟React源码的时候,我们使用的是effects数组来存储当前fiber节点的所有子节点。我们可以将这三个属性理解为effectsList节点,理解为副作用的列表容器,它是单链表结构,firstEffect是子树种第一个side effect,lastEffect是最后一个,中间的所有effect都是nexteffect进行存储。如图所示:

4. expirationTime
存储的是任务的过期时间。如果因为任务优先级的关系,任务迟迟没有得到执行,如果超过任务过期时间,react会强制执行该任务。如果是同步任务这个过期时间会被设置成一个很大的数值,在我们后面调试源码的时候,如果你看到这个值是一个比较大数值,我们应该想到他是一个同步任务。
5. mode
表示当前Fiber节点的模式,模式的值可以参考 TypeOfMode(位置: src/react/packages/react-reconciler/src/ReactTypeOfMode.js)
0表示同步渲染模式,1表示严格模式,10表示异步渲染过渡模式,100 异步渲染模式,1000 性能测试模式,在react当中组件也分为很多种,不同组件可以使用不同的模式。
export type TypeOfMode = number;
// 0 同步渲染模式
export const NoMode = 0b0000;
// 1 严格模式
export const StrictMode = 0b0001;
// 10 异步渲染过渡模式
export const BlockingMode = 0b0010;
// 100 异步渲染模式
export const ConcurrentMode = 0b0100;
// 1000 性能测试模式
export const ProfileMode = 0b1000;
复制代码
以上就大概讲解了一些fiber主要属性的含义。
双缓存技术
在React中,DOM的更新采用了双缓存技术,双缓存技术致力于更快速的DOM更新。
什么是双缓存技术呢?
举个例子
举个例子,使用canvas绘制动画时,在绘制每一帧前都需要清除上一帧的画面,清除上一帧需要花费时间,如果当前帧画面计算量又比较大,又需要花费比较长的时间,这就导致上一帧清除到下一帧显示中间会有较长的间隙,就会出现白屏。
解决方案
为了解决这个问题,我们可以在内存中绘制当前帧动画,绘制完之后直接用当前帧替换上一帧画面,这样的话在帧画面替换的过程中就会节约非常多的事件,就不会出现白屏问题,这种在内存中构建并直接替换的技术叫做双缓存
React中实现思路
React使用双缓存技术完成Fiber书的构建与替换,实现DOM对象的快速更新。
在React中最多会同时存在两颗Fiber树,当前在屏幕中显示的内容对应的Fiber树叫做Current Fiber树,当发生更新时,React会在内存中重新构建一颗新的Fiber树,这颗正在构建的Fiber树叫做workinProgress Fiebr树,在双缓存技术中,workinProgress Fiber树就是即将要显示在页面中的Fiber树,当这颗Fiber树构建完成后,React会使用它直接替换current Fiber树达到快速更新DOM的目的,因为workInProgress Fiber树是在内存中构建的所以构建他的速度是非常快
一旦workInProgress树在屏幕上出现,他就变成current Fiber树
具体实现的数据结构
current Fiber树 和 workProgress Fiebr树是存在联系的,因为每次构建workProgress Fiber树的时候并不是全部的fiber树属性的构建,实际上很多属性是直接可以复用current Fiber树的。所以在代码层面两者需要建立关联关系,这个关联关系是如何建立的呢?
在current Fiber节点对象中有一个alternate属性指向对应的workProgress Fiebr节点对象,在workInProgress节点中也有一个alternate属性也指向对应的currentFiber节点对象。他们关联关系就是依靠上面所讲的alternate属性进行实现的。

这张图呢在最顶端是fierRootNode对象(先忽略),在最左侧rootFiber表示的是组件的挂载点对应的fiber对象(即对应的id为root的div对应的fiber对象),react在初始渲染时,会先构建这个div所对应的fiber对象,构建完这个fiber对象以后又将这个对象看成是current fiber树,接下来React会在这个fiber对象添加一个属性alternate,属性值是current Fiber对象的拷贝。将拷贝的current Fiebr对象当做workInprogress Fiber树。当然在workInprogress Fiber树种也添加了alternate属性,属性值指向的是currentfiber树。接下来的工作构建子级fiber对象的工作就在workInprogress fiber树种进行。比如app组件 和 p元素的构建。当所有fiber对象构建完成后以后,使用workInputProgress fiber树替换current Fiber树。

这样就完成了fiber节点的构建和替换了。替换完成后,workInProgress Fiber树就替换成了current Fiber树。Fiber里面是存储了DOM节点对象的,也就是说DOM对象的构建呢是在内存中完成构建的。当所有的fiber对象完成构建以后,DOM对象也构建完成后了。这样就可以使用内存中的DOM对象替换页面中的DOM对象了。
这就是react当中使用的双缓存技术,目的就是实现更快速的DOM更新。
区分fiberRoot(fiber对象的根) 和 rootFiber(就是根DOM对应fiber对象)
fiberRoot 表示Fiber数据结构对象,是Fiber数据结构中的最外层对象
rootFiber表示组件挂载点对应的Fiebr对象,比如React应用中默认的组件挂载点就是id为root的div
fiberRoot包含rootFiber,在fiberRoot对象有一个current属性,存储的就是rootFiber
rootFiber指向fiberRoot,在rootFiber对象中有一个stateNode属性,指向fiberRoot
在React应用中FiberRoot只有一个,而rootFiber可以有多个,因为render方法可以调用多次的
fiberRoot会记录应用的更新信息,比如协调器在完成工作后,会将工作成果存储在fiberRoot中。

总结
在这篇学习笔记中,我们介绍了React16库的架构和相关技术。这里我们回顾一下今天介绍的知识点:
React16的架构可以分为三层:调度层,协调层,渲染层。
调度层,因为React15采用的递归加循环,递归不能停止导致页面会出现卡顿问题,需要解决这个问题react首先采用循环,然后需要在高优任务(页面渲染等)执行完成之后执行低优先fiber构建和比对任务,且低优先任务是可以暂停打断一种实现方案,window全局也有requestIdleCallback可以实现,但是有些浏览器还不能兼容,也不稳定。所以React自己完成了调度层的实现,做到了调度任务的优先级,高优任务可以优先进入协调器的工作
然后在协调层中使用循环,完成构建Fiber数据结构,比对Fiber对象找出差异,记录Fiber对象要进行的DOM操作。这里将VirtualDOM树的节点生成一个个小的Fiber对象,构建一个Fiber树,也就拆分成一个个小任务,在浏览器闲暇的时候执行一个个小任务完成fiber对象的比对,而且使用双缓存技术将DOM在内存中即可完成构建和替换,为实现更快的更新DOM打下基础
最后在renderer层将发生变化的部分渲染到页面上,调度器和协调器工作是在内存中是可以被打断的,渲染器在这个阶段是不可以被打断的。
希望通过这次对React16架构和相关的技术的介绍,大家会对React16有一个系统的认知,日拱一卒,欢迎点赞,评论探讨,关注,加油!!!























![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)