React Fiber详解

问题是什么?

我们先看一个例子:
https://claudiopro.github.io/react-fiber-vs-stack-demo/stack.html
在这里插入图片描述

在上面这张图片中,页面出现一卡一卡的现象。

为什么人眼会感觉到卡顿?

当浏览器每秒刷新的次数低于60hz人眼就会感知卡顿掉帧等情况。

FPS

浏览器每秒刷新的次数称为 FPS(frame per second)。

浏览器的一帧说的就是一次完整的重绘。

理论上FPS越高人眼觉得界面越流畅,在两次屏幕硬件刷新之间,浏览器正好进行一次刷新(重绘),网页也会很流畅,当然这种是理想模式, 如果两次硬件刷新之间浏览器重绘多次是没意义的,只会消耗资源,如果浏览器重绘一次的时间是硬件多次刷新的时间,那么人眼将感知卡顿掉帧等, 所以浏览器对一次重绘的渲染工作需要在16ms(1000ms/60)之内完成,也就是说每一次重绘小于16ms才不会卡顿掉帧。

实际上,对用户来说,不良的体验不只是视觉上表现为卡顿与掉帧,因为在浏览器中,JS 运算、页面布局和页面绘制都是运行在浏览器的主线程当中,他们之间是互斥的关系。通常这时,对于用户在输入框输入内容这个行为来说,就体现为按下了键盘按键但是页面上不实时显示输入。
用户的交互得不到及时的响应,这对网页的用户体验来说是非常不利的。

React 15架构

Reconciler 协调器

每当有更新发生时,Reconciler会做如下工作:

  • 调用函数组件、或class组件的render方法,将返回的JSX转化为虚拟DOM
  • 将虚拟DOM和上次更新时的虚拟DOM对比
  • 通过对比找出本次更新中变化的虚拟DOM
  • 通知Renderer将变化的虚拟DOM渲染到页面上

Renderer(渲染器)

  • ReactDOM渲染器,浏览器环境渲染
  • ReactNative渲染器,渲染App原生组件
  • ReactTest渲染器,渲染出纯Js对象用于测试
  • ReactArt渲染器,渲染到Canvas, SVG 或 VML (IE8)

在每16.6ms时间内,需要完成如下工作:
JS脚本执行 ----- 样式布局 ----- 样式绘制

对于React的更新来说,递归遍历应用的所有节点由于递归执行,计算出差异,然后再更新 UI。递归是不能被打断的,所以更新一旦开始,中途就无法中断。当层级很深时,递归更新时间超过了16ms,用户交互就会卡顿。

另一方面,递归的方式进行渲染,使用的是 JS 引擎自身的函数调用栈,当层级很多的,可能会出现爆栈(stack overflow)的错误。当然这是递归的另一个缺点,但并不是React要优化的主要原因。

如何解决?

既然递归的不可打断的计算更新阻塞了用户的交互,有没有可能做到这样的效果:当浏览器有空闲时间就正常进行计算更新,而当有其他优先级更高的事件需要响应时就先暂停计算,去响应优先级高的事件,响应结束之后再接着刚才的计算继续进行,直到结束。

React 16的设计思想

React 16实现的思路是这样的:将运算切割为多个步骤,分批完成。说在完成一部分任务之后,将控制权交回给浏览器,让浏览器有时间进行页面的渲染。等浏览器忙完之后,再继续之前未完成的任务。这就是React 16中的Fiber设计思想。

有了解题思路后,我们再来看看 React 16具体是怎么做的。

React 16的实现

为了加以区分,以前的 Reconciler 被命名为Stack Reconciler。Stack Reconciler 运作的过程是不能被打断的,必须一条道走到黑:
在这里插入图片描述

而 Fiber Reconciler 每执行一段时间,都会将控制权交回给浏览器,可以分段执行:
在这里插入图片描述

为了达到这种效果,就需要有一个调度器 (Scheduler) 来进行任务分配。任务的优先级有六种:

  • synchronous,与之前的Stack Reconciler操作一样,同步执行
  • task,在next tick之前执行
  • animation,下一帧之前执行
  • high,在不久的将来立即执行
  • low,稍微延迟执行也没关系
  • offscreen,下一次render时或scroll时才执行
    优先级高的任务(如键盘输入)可以打断优先级低的任务(如Diff)的执行,从而更快的生效。

Fiber Reconciler 在执行过程中,会分为 2 个阶段。
在这里插入图片描述

  1. 阶段一,生成 Fiber 树,得出需要更新的节点信息。这一步是一个渐进的过程,可以被打断。
  2. 阶段二,将需要更新的节点一次过批量更新,这个过程不能被打断。
    阶段一可被打断的特性,让优先级更高的任务先执行,从框架层面大大降低了页面掉帧的概率。

看一下React 16下的效果
https://claudiopro.github.io/react-fiber-vs-stack-demo/fiber.html

在这里插入图片描述

可以看到,动画变得顺滑很多。

接下来看下Fiber的具体细节。

Fiber的实现细节

Fiber Reconciler 在阶段一进行 Diff 计算的时候,会生成一棵 Fiber 树。这棵树是在 Virtual DOM 树的基础上增加额外的信息来生成的,它本质来说是一个链表。

Fiber的结构

每个Fiber节点有个对应的React element,多个Fiber节点是如何连接形成树呢?靠如下三个属性:

// 指向父级Fiber节点
this.return = null;
// 指向子Fiber节点
this.child = null;
// 指向右边第一个兄弟Fiber节点
this.sibling = null;

举个例子,如下的组件结构:

function App() {
   
  return (
    <div>
      i am
      <span>KaSong</span>
    </div>
  )
}

对应的Fiber树结构:
在这里插入图片描述

作为一种静态的数据结构,保存了组件相关的信息:

// Fiber对应组件的类型 Funct
  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值