目录
1.什么是Virtual Dom以及为什么要使用Virtual Dom
五、React16.0之前的架构为什么不能实现可中断、可恢复呢
七、react16.x如何实现给不同的任务赋予不同的优先级?
最近在学习react底层原理的时候,发现刚开始学习的时候对于Fiber的理解一直处于比较懵逼的状态,看文章和视频基本都会出现“碎片化”,“可中断”,“可恢复”,“优先级”,“将控制权交还给浏览器”等字眼,理解上也处于断断续续的那种,于是就根据一些大神写的文章再根据自己的理解重新整理了一下,希望能帮到跟我有同样困惑的小伙伴呀。
讲到React Fiber那肯定跟虚拟Dom,diff算法脱不了关系的,为了后续更好的理解Fiber的设计思想,我会顺带的讲一下Virtual Dom diff算法
一、Virtual Dom
1.什么是Virtual Dom以及为什么要使用Virtual Dom
Virtual Dom是通过js模拟dom节点的对象。
Facebook在构建react初期,考虑到要提升代码抽象能力,避免人为的DOM操作、降低整体代码风险等因素,所以引入了虚拟DOM
2.Virtual Dom在React中的体现
在React中,jsx文件在Babel插件的作用下编译成React.createElement函数,而React.createElement函数运行之后会返回一个Plain Object,它描述了dom节点的tag类型,props属性以及children情况等,这些Plain Object通过树形结构组成一棵虚拟dom树。
3.Virtual Dom的优缺点
优点:改善大规模操作DOM的性能、由于内部进行了字符转义,所以还可以XSS风险、可跨平台
缺点:因为需要模拟整个网页的真实DOM,所以内存占用较高、除此之外虚拟dom没办法做到极致的优化。
二、diff算法
1.什么是diff算法
diff算法一定是建立在虚拟dom的基础上的。在虚拟dom树发生变化后,通过对比新旧两株虚拟dom树的变更差异,将更新补丁作用于真实dom,以最小成本完成视图更新的方式。
2.react16.x中对diff算法的优化策略
react16.x中将diff算法的复杂度由O(n^3)降低为O(n),分别从树、组件及元素三个层面进行复杂度的优化:
-
树比对:忽略节点跨层级操作场景,提升比对效率。两棵树只对同一层次的节点进行比较,如果发现节点已经不在了,则该节点及其子节点会被完全删除,不会再进一步进行比较,提升了比对效率。
-
组件对比:拥有相同类的两个组件生成相似的树形结构,拥有不同类的两个组件会生成不同的属性结构
-
同一层级的子节点,可以通过标记key的方式进行列表对比,元素比对主要发生在同层级中,通过标记key的方式,react可以直接移动DOM节点,降低内耗。
简单了解了Virtual Dom和diff算法之后我们再来聊聊我们今天的主角“Fiber”吧。
三、什么是Fiber
Fiber就是比线程还要纤细的一个过程,意在对渲染过程实现更加精细的控制
从架构角度来看,Fiber是对React核心算法(即调和过程)的重写
从编码角度来看,Fiber是React内部所定义的一种新的数据结构,它是Fiber树结构的节点单位,也就是React 16下的“虚拟DOM”,它使得diff阶段有了被保存工作进度的能力
从工作流的角度来看,Fiber节点保存了组件需要更新的状态和副作用,一个Fiber同时对应着一个工作单元。
四、为什么出现Fiber
Fiber是React16.x才有的概念,Fiber架构应用的目的是实现“增量渲染”,即把一个渲染任务分解为多个渲染任务,然后将其分散到多个帧里面,最终目的是为了实现任务的可中断、可恢复并给不同的任务赋予不同的优先级,最终达到更加顺滑的用户体验。
看到这里的时候心里难免会有以下疑问,
React16.0之前的架构为什么不能实现可中断、可恢复呢
为什么只有React Fiber而没有Vue Fiber呢等等
那么带着疑问我们继续学习React16.x的运行原理吧
五、React16.0之前的架构为什么不能实现可中断、可恢复呢
- 在老的架构中,节点以树的形式被组织起来,每个节点上有多个指针指向子节点。要找到两棵树的变化部分,需要进行深度优先遍历。这种遍历有一个特点,必须一次性完成。假设遍历发生了中断,虽然可以保留当下进行中节点的索引,下次继续时,没有办法找到其父节点。因为每个节点没有指向父节点的指针。
- 在新的架构中,每个节点有三个指针,分别指向第一个子节点、下一个兄弟节点、父节点。这种数据结构就是fiber
fiber和树虽然数据结构不同,但是节点的遍历开始和完成顺序一模一样,不同的是当遍历发生中断时,只要保留下当前节点的索引,断点是可以恢复的,因为每个节点都保持着对其父节点的索引。树和fiber虽然看起来很像,但本质上来说一个是树,一个是链表。
fiber的开启暂停可以被程序员所控制。react fiber是通过requestIdleCallback这个api控制的组件渲染的“进度条”。
requesetIdleCallback是一个属于宏任务的回调,受屏幕的刷新率去控制,每隔16ms会被调用一次。它的回调函数可以获取本次可以执行的时间。需要注意的是,由于其兼容性不好,加上该回调函数被调用的频率太低,react实际使用的是一个polyfill(自己实现的api),而不是requestIdleCallback.
六、为什么vue架构不需要引入Fiber呢?
我们都知道,在react应用中,当某个组件的状态发生变化时,它会以该组件为根,重新渲染整个组件子树,因此在数据更新时,react组件相比vue组件会渲染出一棵更大的虚拟dom树,给后续的diff算法带来了很大的压力,js占据主线程去做比较,渲染线程便无法做其他工作,用户的交互得不到响应,导致页面卡顿
fiber使得diff的过程被分成一小段一小段的,js会比较一部分虚拟dom,然后让渡主线程,给浏览器去做其他优先级较高的工作,然后继续比较,依次往复,等到最后比较完成,一次性更新到视图上。
而vue基于数据劫持,更新粒度很小,没有这个压力。
七、react16.x如何实现给不同的任务赋予不同的优先级?
fiber的出现还实现了给不同任务赋予不同的优先级,然后优先实现高优先级的任务。那么我们就从架构方面学习一下react16.x是如何实现给不同的任务赋予不用的优先级的
在 React 16 之前,React 的渲染和更新阶段依赖的是如下图所示的两层架构:
Reconciler这一层负责对比出新老虚拟DOM之间的变化,Renderer这一层负责将变化的部分应用到视图上,从Reconciler到Renderer这个过程是严格同步的。
在React16中,为了给每个任务赋予不同的优先级,两层架构变成了三层架构:
多出来的这层架构叫做“Scheduler(调度器)”,调度器的作用是调度更新的优先级。
在这套架构模式下,更新的处理工作就变成了:每个更新任务都会赋予一个优先级,当更新任务抵达调度器时,高优先级的更新任务会更快地被调度进Reconciler层,此时若有新的更新任务抵达调度器,调度器会检查它的优先级,优先级高的会被优先推入Reconciler层