React Fiber

在了解一个知识架构的时候,为了能更彻底的了解这个知识架构,我们通常会围绕着以下几个问题展开来理解。

  • 什么是React Fiber
  • React Fiber产生的原因(React Fiber解决了什么问题)
  • 详细了解React Fiber

1.什么是fiber

首先fiber是操作系统的概念,即纤程。是更轻量级的线程,一个线程可以包含多个纤程。React在这里利用这个特点(注意是特点,意图相同)来命名React Fiber。

2.React Fiber产生的原因

Fiber诞生之前,React运行机制是采用一把梭的方式进行虚拟dom对比以及更新视图,大致过程如下

第一步:首先创建一颗虚拟dom tree

第二步:在状态改变的时候生成一颗新的虚拟dom tree

第三步:两个树进行对比得到更新的节点

第四步:将得到的更新节点对应生成真实dom

这种一把梭的流程弊端就是js执行时间过长导致UI卡顿甚至掉帧。至于为何UI卡顿或者掉帧呢~到这里就需要考虑一些关于浏览器原理问题。

浏览器原理

浏览器是多线程运行,包含JavaScript线程,事件线程,定时任务线程,HTTP请求线程,UI渲染线程等等,但是这里JS线程与UI线程是互斥的,

也就说是这俩只能执行其中一个线程,每当一个线程在执行的时候,另一个线程都会被挂起,原因很简单,JS线程可以操作dom,如果同时执行

会带来很多不可控问题。当JS线程执行时间过长的时候就会导致UI渲染线程一直被挂起,可操作时间就会一直推迟,导致的效果就是页面不响应,

卡顿,甚至掉帧的可能。

React Fiber出来之前,传统模式的React再进行协调(这里我只考虑这个阶段,因为映射真实dom阶段不会有太大的消耗)会有比较大的性能开销,

当然React在这个阶段已经做了这个优化,比如优化虚拟dom tree对比的过程。但在复杂场景仍然会导致这个问题。好了既然说到JS执行时间过长,那么

多少时间算合理呢~

通常设备的帧率是60次/秒,也就是一帧需要控制在16.67ms之内才能保证一个非常理想的交互体验,浏览器的一帧组成如同

 

从浏览器一帧的结构中可以观察到,渲染之间还有events,javascript,begin frame,rAF要处理,一帧中,需要将JS执行时间控制到一定范围,

才不会影响渲染。所以React Fiber主要的任务就是将传统React协调方式进行优化,从而达到每次运行可以控制在一个合理的范围,达到一个理

想的UI交互,这就是React Fiber要解决的问题。

ps:这里不考虑UI渲染部分的耗时。

3.详细了解React Fiber

 React Fiber

(1)React Fiber 在reconciliation阶段的协调核心算法重新实现

(2)链表结构的Fiber tree,方便在reconciliation阶段做中断以及回溯

(3)可控调用栈

React Fiber重新实现

首先了解一帧的执行过程

 

在一帧中执行可以通过插入requestIdleCallback来执行定向操作,但是requestIdleCallback调用是在渲染之后,这就导致了,我们不可以在这里

再进行dom操作,再次操作dom会导致重新渲染,导致结果不可控。

 

来自MDN的解释:

window.requestIdleCallback()方法插入一个函数,这个函数将在浏览器空闲时期被调用。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。函数一般会按先进先调用的顺序执行,然而,如果回调函数指定了执行超时时间timeout,则有可能为了在超时前执行函数而打乱执行顺序。(来自:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestIdleCallback)

 

React内部则是避开了requestIdleCallback采用window.requestAnimationFrame来动态调整帧率,计算JS剩余时间是否超过React设定的阈值16ms,从而实现

时间分片,原因就是requestAnmiationFrame再渲染之前执行,在这里做任何操作都不会影响到渲染的结果。React Fiber 规定一帧内JS执行的最大时间,即阈值16ms

 

来自MDN的解释:

window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行

注意:若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用window.requestAnimationFrame()

 

JS在执行过程,一个工作单元由JS引擎启动,一把梭得形式执行,只要开始就无法中断,直到整个工作单元执行结束。如图

 

 

React Fiber得目的是将一个繁重得任务进行拆分,拆分成一个个小的单元来进行执行,(这里就是fiber纤程得概念)按照优先级高低得方式

进行执行这一个个小的单元任务,这就是时间分片得概念。如下图

 

 

总结Fiber 大致调度过程

(1)state改变执行一个同步任务,将其拆分成一个任务队列

(2)在任务队列中按照任务优先级执行,如果执行超过了deadline,这状态设置为pending状态挂起

(3)一个fiber任务的执行或者挂起,根据requestIdleRequest/requestAnimationRequest实现的调度器,返一个新的fiber任务队列继续执行。

 

 

 任务优先级

sync

InteractiveExpiration 

AsyncExpiration

开发者视角

unstable_scheduleCallback(() => {
  // 异步低优先级任务,AsyncExpiration
  this.setState();
});
flushSync(() => {
  // 同步任务,最高优先级
  this.setState();
});
onClick={() => {
  // 异步高优先级任务,InteractiveExpiration
  this.setState();
}}

Fiber节点

一个fiber结构是一个js对象,对应通过React.createElement来创建一个虚拟dom节点,整个虚拟dom树上得所有节点都对应一个fiber节点,这个fiber节点上

除了包含元素信息意外,还包含了一些其他信息,用来支撑整个调度算法。

fiber节点上重要得信息就是包含了三个“指针”,分别是child,sibling,return,用来遍历整个fiber树。

child指向第一个儿子节点(其余儿子节点都是基于第一个儿子节点通过sibling查找)

sibling指向兄弟节点

return指向父级节点

Fiber 树的结构图

 

 

得益于fiber节点指针以及整颗树得链表结构,可以实现任务分片,任务中断,按照优先级调度。

整个fiber树执行过程

render阶段

1创建当前current树

2状态改变生成workInProgress树

3协调生成effecList链表(变更得节点链表)

commit阶段

4将effectList链表映射到真实dom

Update更新过程

(1)记录节点状态

(2)将变更节点放到updateQueue中

(3)统一更新到UI视图层

setState干了什么

setState本质上只是一个改变state得命令,可以理解为是一个视图更新得请求,但是试图更新不是setState干得事儿。当执行了setState得时候

改变了state,得到变更得fiber节点,然后将变更得节点push到了updateQueue中,在React会统一在一个时间点处理整个updateQueue内得节点

我们可以观察到一个很常见得事儿,就是在调用setState方法之后state并没有及时更新。上述就是原因,举个例子

当在一个合成事件内同时执行了多个setState方法,比如针对一个count,多次进行了+1操作,得到得结果state只是1。原因就是,多次执行的时候

第一次执行setState的时候count进行+1的操作,但是state并没有及时改变,再次执行的时候state还是原来的值,这样多次操作其实效果就是一次操作

的效果,只是连续往updateQueue中push了count+1的第一次得到的值。

posted @ 2022-01-05 14:24  漠然0408丶  阅读(577)  评论(0编辑  收藏  举报