fiber架构思路

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了fiber架构思路相关的知识,希望对你有一定的参考价值。

参考技术A react在16+的版本中使用fiber架构解决了很多的问题。
react在调用this.setState()的过程中使用的脏处理(从root节点开始调用),在以前的版本中就会导致卡顿,尽管有虚拟dom,diff算法,但是如果递归的足够深,如div-div一直有100个div,在重新渲染阶段会造成卡顿现象,如在渲染过程中不能触发点击事件等,也正是因为这个原因,fiber架构的出现就是为了解决递归过深,会造成无可避免的卡顿这个问题。

fiber架构采用了chrome浏览器的一个api requestIdleCallback(callback[, options]),在浏览器的空闲阶段调用,这里可能会扯很多浏览器的概念,由于这个api目前只支持谷歌,所以react也对此方法进行了重写。

浏览器的空闲阶段调用究竟是怎么回事呢?就必须要说一说浏览器了。
浏览器是一个多进程。Browser 进程,第三方插件进程,gpu进程,和重中之重浏览器内核。

下面我们说一下浏览器内核,一个进程可以有多个线程。那浏览器内核也不为过
如gui渲染线程,js引擎线程,定时器线程,事件触发线程,http请求线程。
我们经常会说gui渲染线程和js引擎线程互斥,因为js在浏览器中运行也可以操作dom,为了防止渲染出现问题,所以互斥,就说到这里。

浏览器在每一帧(时间片)都是在工作的,和我们人一样,上班都需要工作,只是在工作的时候没有活,我们可以偷懒一会。
一个看上去不卡的浏览器,必须需要1s中完成60帧,也就是16ms为一帧,当你的显卡更好的时候,肯定是不止60帧这个数值。但是这里就使用60帧,这个概念,所以说打游戏的时候fps低帧数低,每秒加载的帧数低就算网络不卡,你也会玩起来很卡。
每一帧
1.首先会先进行事件处理,如果有事件触发了且有回调函数,如触发点击事件,且有回调函数,会把回调函数放到eventLoop的宏任务队列中。
2.然后执行js,在js调用栈为空,先去执行宏任务队列最先执行的,再执行所有微任务队列里面的方法,执行完再去执行js调用栈,来回循环调用。
3.执行requestanimationframe,再重绘之前执行
4.解析html --先把html解析成dom树,构建渲染树,布局渲染树,绘制渲染树,这也是一个经常会出的面试题,重绘 -重排(重绘-回流)。
5.剩余时间片,好比人工作总会有没事的时候。requsetIdleCallback就是再剩余时间片执行,如果这个帧没有剩余时间片,那就下一个帧寻找是否有剩余时间片。

我们渲染阶段放到requestIdleCallback中,之前的递归必须递归到底,但是现在我可以放到每一个时间片之中,requestIdleCallback(workLoop,timeout:1000),再workloop方法中我再调用requestIdleCallback(workLoop,timeout:1000),这样每一帧的空闲时间我都会去调用workloop方法,这样我就不需要一次性递归到底,也就避免了造成卡顿。

下面我来说一说fiber是怎么实现的。
这里说的肯定不可能太具体,只能讲一点思路,下次写博客深入理解fiber机构。

如果了解过一点fiber的数据结构知道,fiber的数据结构中有

React.createElement是为了帮我们创建虚拟dom的。我们需要通过虚拟dom去创建对应的fiber。

componentDidmounted是从外到内执行的,好比先序遍历,而componentWillUnmounted是从里往外执行,后序遍历。
那么fiber调用的时候肯定就会出现这两个阶段。
在先序遍历的时候我们调用beginWork 根据先给fiber.stateNode=dom/new实例,在给其子节点创建fiber
在后续遍历中,我们调用completeUnitOfWork,完成effectlist,

effectlist 先调用根节点的firstEffect即d,然后一次nexteffect,即d-b-c-a

最后在commit阶段根据effectList,依次appendChild,removeChild,或者更新属性,完成渲染。

react为何采用fiber架构

这里要对比一下stack和fiber架构的不同以及react在fiber架构做了那些更改

这里说到了react16使用了fiber,那我们看下16之前输入stack架构的实现的问题,说起React算法架构避不开“Reconciliaton”。

Reconciliation

React 官方核心算法名称是 Reconciliation , 中文翻译是“协调”![React diff 算法的实现就与之相关。
稍微了解浏览器加载页面原理的前端同学都知道网页性能问题大都出现在DOM节点频繁操作上;
而React通过“虚拟DOM” + React Diff算法保证了前端性能

传统Diff算法

通过循环递归对节点进行依次对比,算法复杂度达到 O(n^3) ,n是树的节点数,这个有多可怕呢?——如果要展示1000个节点,得执行上亿次比较。。即便是CPU快能执行30亿条命令,也很难在一秒内计算出差异。

React Diff算法

将Virtual DOM树转换成actual DOM树的最少操作的过程 称为 协调(Reconciliaton)。
React Diff三大策略 :

  • tree diff
  • component diff
  • element diff

在V16版本之前 协调机制 是 Stack reconciler, V16版本发布Fiber 架构后是 Fiber reconciler。

我们先说Stack reconciler存在的问题:
在setState后,react会立即开始reconciliation过程,从父节点(Virtual DOM)开始递归遍历,以找出不同。将所有的Virtual DOM遍历完成后,reconciler才能给出当前需要修改真实DOM的信息,并传递给renderer,进行渲染,然后屏幕上才会显示此次更新内容。

对于特别庞大的DOM树来说,reconciliation过程会很长(x00ms),在这期间,主线程是被js占用的,因此任何交互、布局、渲染都会停止,给用户的感觉就是页面被卡住了。

在这里我们想解决这个问题的话,来引入一个概念,就是任务可中断,以及任务优先级,也就是说我们的reconciliation的过程中会生成一些任务和子任务,用户的操作的任务优先级是要高于reconciliation产生的任务的,也就是说用户操作的任务是可以打断reconciliation中产生得任务的,它会优先执行.

Fiber reconciler

原来的React更新任务是采用递归形式,那么现在如果任务想中断, 在递归中是很难处理, 所以React改成了大循环模式,修改了生命周期也是因为任务可中断。

Fiber reconciler 使用了scheduling(调度)这一过程, 每次只做一个很小的任务,做完后能够“喘口气儿”,回到主线程看下有没有什么更高优先级的任务需要处理,如果有则先处理更高优先级的任务,没有则继续执行(cooperative scheduling 合作式调度)。

所以Fiber 架构就是用 异步的方式解决旧版本 同步递归导致的性能问题。

本文借鉴于
https://segmentfault.com/a/11...

以上是关于fiber架构思路的主要内容,如果未能解决你的问题,请参考以下文章

react为何采用fiber架构

手写React的Fiber架构,深入理解其原理

推荐手写React的Fiber架构,深入理解其原理

前端大佬谈 React Fiber 架构

React Fiber架构原理

React源码之Fiber架构