前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React_Fiber机制(下)

React_Fiber机制(下)

作者头像
前端柒八九
发布2022-08-25 15:14:27
1.2K0
发布2022-08-25 15:14:27
举报
文章被收录于专栏:柒八九技术收纳盒

大家好,我是「柒八九」

前段时间,我们开辟了,「前端框架」的文章系列,首先就介绍了,关于React-Fiber的相关机制。由于文章行文结构所制约下,针对一些边界情况,没有展开介绍。

而今天的这篇文章,就是为了查漏补缺的。有些比较重要的点,可能会再次提出。

好了,「话不多说,开搞」

你能所学到的知识点

  1. React-Fiber是个啥
  2. React旧有的堆栈调和器Stack Reconciler存在什么问题
  3. 页面丢帧的原因
  4. React-Fiber的工作原理

文章概要

  1. React-Fiber是个啥
  2. 堆栈调和器Stack Reconciler
  3. 递归操作
  4. React Fiber 如何工作的

1. React-Fiber是个啥

React Fiber是一个「内部引擎」,旨在使 React 更快、更智能。 ❞

Fiber 调和器Fiber Reconciler成为 React 16+版本的「默认调和器」,它完全重写了 React 原有的调和算法,以解决 React 中一些长期存在的问题。

因为 Fiber 是异步Asynchronous的,React可以:

  • 当新的更新发生时,「暂停」「恢复」「重新启动」组件的渲染工作
  • 「重复使用」以前完成的工作,如果不再需要,甚至可以丢弃它
  • 「工作分成几块」,并根据「重要性」来确定任务的优先次序

❝在「调和」过程中有很多操作, 例如「调用生命周期方法」或者更新ref等。所有这些操作在 Fiber 架构中都被统称为 工作Work。 「工作的类型通常取决于React元素的类型」

这一变化使 React 摆脱了同步堆栈调节器Synchronous Stack Reconciler的限制。以前,你可以添加或删除组件,但「必须等调用堆栈为空,而且任务不能被中断」

使用新的调节器,也「确保最重要的更新尽快发生」。(更新存在优先级)

在了解Fiber 调和器之前,我们先来简单了解下原来的调节算法:「堆栈调和器」


2. 堆栈调和器Stack Reconciler

❝为什么这被称为 "堆栈 "调节器?这个名字来自于 "堆栈 "数据结构,它是一个「后进先出」的机制。 ❞

我们从最熟悉的ReactDOM.render(<App />, document.getElementById('root'))语法开始探索。

ReactDOM 模块将<App/ >传递给调和器,但这里有两个问题:

  • <App />指的是什么?
  • 什么是调和器?

让我们来一一解答这些问题。

<App />指的是什么?

<App />是一个React元素。根据 React博客描述,”元素是一个描述组件实例DOM节点及其所需属性的「普通对象」“。

换句话说,元素「不是实际的DOM节点或组件实例」;它们是一种向 React 描述它们是什么类型的元素,它们拥有什么属性,以及它们的孩子是谁的信息组织方式。

React 元素在早期的React介绍文档中,有另外一个家喻户晓的名字:「虚拟DOMVirtual-DOM」 只不过,V-Dom在理解上在某些场景下会产生歧义,所以逐渐被React 元素所替代 ❞

这就是 React 的真正力量所在。React 将如何构建渲染管理实际DOM树的生命周期的复杂部分「抽象出来」,有效地使开发者的开发变得更容易。

为了理解React 元素所带来的好处,让我们看一下使用面向对象Object-Oriented的传统方法解决一个页面逻辑的开发,到底经历些什么。

React中的OOP(面向对象编程)

在传统的面向对象编程中,开发者必须实例化管理每个DOM元素的生命周期。例如,如果你想创建一个简单的表单和一个提交按钮,它们的状态信息仍然需要开发者来维护。

让我们假设 Button 组件有一个 isSubmitted 「状态变量」Button 组件的生命周期看起来像下面的流程图,其中「每个状态都必须由开发者管理」

流程图的大小代码行数随着状态变量数量的增加而呈「指数级增长」

所以,React 使用元素来解决这个问题;在 React两种元素「DOM元素」「组件元素」

  • 「DOM元素是一个字符串的元素」 例如,<button class="okButton"> OK </button>
  • 「组件元素是一个类或一个函数」 例如,<Button className="okButton"> OK </Button>,其中 <Button>「一个类或一个函数组件」

❝这两种类型都是「简单的对象」。 它们仅仅是对在屏幕上「渲染的内容的描述」,在你创建和实例化它们的时候,「并不会发生渲染操作」。 ❞

React 调和算法Reconciliation

该算法使得 React 更容易解析和遍历应用,用以建立对应的DOM树「实际的渲染工作会在遍历完成后发生」

React 遇到一个类或一个函数组件时,它会基于元素的props来渲染UI视图。

例如,如果<App>组件渲染了以下内容,那么 React 会遍历<Form><Button>组件,它们想根据相应的 props 渲染成什么。

代码语言:javascript
复制
<Form>
  <Button>
    Submit
  </Button>
</Form>

Form 组件是函数组件,React 将调用render()来了解它所要渲染的元素,得知它要渲染一个有孩子节点的<div>

代码语言:javascript
复制
const Form = (props) => {
  return(
    <div className="form">
      {props.form}
    </div>
  )
}

React「重复这个过程」,直到它掌握了页面上与每个组件所对应的DOM元素的相关渲染信息。

❝这种通过「递归元素树」,以掌握React应用的组件树的DOM元素的过程,被称为「调和」。 ❞

在调和结束时,React 知道DOM树的结果,像 react-domreact-native 这些「渲染器」渲染更新DOM节点所需的「最小变化集」。这意味着,当你调用 ReactDOM.render()setState()时,React 就会执行调和处理。

setState 的情况下,它执行了一个遍历,并通过「将新的树与渲染的树进行比较」来确定树中的变化。然后,它将这些变化应用到「当前树」上。

3. 递归操作

在上文介绍「堆栈调和器」中得知,在进行调和处理时,会执行「递归操作」,而递归操作和「调用栈」有很大的关系,进而我们可以得出,递归和「堆栈」也有千丝万缕的联系。

用一个简单的例子,看看在「调用栈」中会发生什么。

代码语言:javascript
复制
function fib(n) {
  if (n < 2){
    return n
  }
  return fib(n - 1) + fib (n - 2)
}

fib(3)

我们可以看到,调用堆栈将对fib()的每一次调用都「推入堆栈」,直到弹出fib(1)(第一个返回的函数调用)。

我们刚才看到的调和算法是一个「纯粹的递归算法」一个更新会导致整个子树立即重新渲染。虽然这很好用,但这也有一些局限性。

❝在用户界面中,「没有必要让每个更新都立即显示」; 事实上,这样做可能会造成浪费,导致「帧数下降并降低用户体验」。 ❞

另外,不同类型的更新「有不同的优先级」--动画更新必须比数据存储的更新完成得快。

页面丢帧dropped frames 问题

帧率Frame Rate

「帧率」是指连续图像出现在显示器上的「频率」。 我们在电脑屏幕上看到的一切都「由屏幕上播放的图像或帧组成,其速度在眼睛看来是瞬间的」。 ❞

可以把电脑显示屏想象成一本书,而书的页面是以某种速度播放的帧。相对而言,电脑显示屏只不过是一本自动翻页书,当屏幕上的事物发生变化时,它就会连续播放。

通常情况下,为了画面流畅和即时,视频的播放速度必须达到「每秒30帧」FPS)左右;任何更高的速度都能带来更好的体验。

现在大多数设备都是以60FPS刷新屏幕,1/60=16.67ms,这意味着「每16ms就有一个新的帧显示」。这个数字很重要,因为如果 React渲染器在屏幕上渲染的时间「超过」16ms,「浏览器就会丢弃该帧」

然而,在现实中,浏览器要做一些「内部工作」,所以你的所有工作「必须在10ms内完成」。当你不能满足这个预算时,帧率就会下降「内容就会在屏幕上抖动」。这通常被称为 jank,它对用户的体验有负面影响。

当然,对于静态和文本内容来说,这并不是一个大问题。但是在显示动画的情况下,这个数字就很关键了。

如果每次有更新时,React 调和算法都会遍历整个App树,并重新渲染,「如果」遍历的时间超过16ms,就会「掉帧」

这也是许多人希望更新按「优先级分类」,而不是盲目地把每个更新都传给「调和器」。另外,许多人希望能够「暂停并在下一帧恢复工作」。这样一来,React可以更好地控制与16ms渲染预算的工作。

这导致React团队重写了调和算法,它被称为Fiber。那么,让我们来看看Fiber是如何解决这个问题的。

4. React Fiber 如何工作的

总结一下实现Fiber所需要的功能

  • 为不同类型的工作分配「优先权」
  • 「暂停工作」,以后再来处理
  • 如果不再需要,就放弃工作
  • 「重复使用」以前完成的工作

实现这样的事情的挑战之一是 JavaScript 引擎的「工作方式」「语言中缺乏线程」。为了理解这一点,让我们简单地探讨一下 JavaScript 引擎如何处理执行上下文。

JavaScript的执行堆栈Execution Stack

每当你在 JavaScript 中写一个函数,JavaScript 引擎就会创建一个函数执行上下文

每次 JavaScript 引擎启动时,它都会创建一个「全局执行上下文」,以保存全局对象;例如,浏览器中的window对象和Node.js中的global对象。JavaScript 使用一个堆栈数据结构来处理这两个上下文,也被称为「执行堆栈」

因此,当存在如下代码时,JavaScript 引擎首先创建一个全局执行上下文,并将其推入执行栈。

代码语言:javascript
复制
function a() {
  console.log("i am a")
  b()
}

function b() {
  console.log("i am b")
}

a()

然后,它为 a()函数创建一个函数执行上下文。由于b()是在a()中调用的,它为b()创建了另一个函数执行上下文,并将其推入堆栈。

b()函数返回时,引擎销毁了b()的上下文。当我们退出a()函数时,a()的上下文被销毁。执行过程中的堆栈看起来像这样。

但是,当浏览器发出像HTTP请求这样的「异步事件」时会发生什么?JavaScript 引擎是储存执行栈并处理异步事件,还是等待事件完成?

JavaScript 引擎在这里做了一些不同的事情:在「执行堆栈的底部」JavaScript 引擎有一个「队列数据结构」,也被称为事件队列Event Queue。事件队列「处理异步调用」

JavaScript 引擎通过等待执行栈清空来处理队列中的项目。所以,每次执行栈清空时,JavaScript 引擎都会检查事件队列,从队列中弹出项目,并处理事件。

❝值得注意的是,只有当「执行栈为空」或者「执行栈中唯一的项目是全局执行上下文」时,JavaScript 引擎才会检查事件队列。 ❞

虽然我们称它们为异步事件,但这里有一个微妙的区别:「事件在到达队列时是异步的,但在实际处理时,它们并不是真正的异步」

回到我们的堆栈调节器,当 React 遍历树时,它在执行堆栈中这样做。所以,当更新发生时,它们会在事件队列中进行「排队」。只有当执行栈清空时,更新才被处理。

这正是Fiber解决的问题,它重新实现了「具有智能功能的堆栈」--例如,暂停、恢复和中止。

Fiber是对堆栈的「重新实现」,专门用于React组件。 可以把一个Fiber看成是一个「虚拟的堆栈框架」。 ❞

重新实现堆栈的「好处」是,你可以把「堆栈帧保留在内存中」,并随时随地执行它们。

简单地说,Fiber代表了「一个有自己的虚拟堆栈的工作单位」。在以前的调和算法的实现中,React 创建了一棵对象树(React元素),这些对象是「不可变」的,并递归地遍历该树。

在当前的实现中,React 创建了「一棵可变的Fiber节点树」Fiber节点有效地持有组件的stateprops和它所渲染的DOM元素。

而且,由于fiber节点可变的,React 「不需要为更新而重新创建每个节点;它可以简单地克隆并在有更新时更新节点」

fiber树的情况下,React 并不执行递归遍历。相反,它创建了一个「单链的列表」,(Effect-List)并执行了一个「父级优先」「深度优先」的遍历。

后记

「分享是一种态度」

参考资料:

  • how-react-fiber-work
  • React官网
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-06-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端柒八九 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 你能所学到的知识点
  • 文章概要
  • 1. React-Fiber是个啥
  • 2. 堆栈调和器Stack Reconciler
    • <App />指的是什么?
      • React中的OOP(面向对象编程)
    • React 调和算法Reconciliation
    • 3. 递归操作
      • 页面丢帧dropped frames 问题
        • 帧率Frame Rate
    • 4. React Fiber 如何工作的
      • JavaScript的执行堆栈Execution Stack
      • 后记
      相关产品与服务
      数据保险箱
      数据保险箱(Cloud Data Coffer Service,CDCS)为您提供更高安全系数的企业核心数据存储服务。您可以通过自定义过期天数的方法删除数据,避免误删带来的损害,还可以将数据跨地域存储,防止一些不可抗因素导致的数据丢失。数据保险箱支持通过控制台、API 等多样化方式快速简单接入,实现海量数据的存储管理。您可以使用数据保险箱对文件数据进行上传、下载,最终实现数据的安全存储和提取。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档