一篇带你了解 React Fiber 是什么?

开发 前端
Fiber,本意为 “纤维”,在计算机世界中则是 ”纤程“ 的意思。纤程可以看作是协程的一种,是一种 任务调度 方式。

大家好,我是前端西瓜哥。

为了提高 React 的性能,React 团队在开发 React 16 时做了底层的重构,引入了 React Fiber 的概念。

React Fiber 是什么?

Fiber,本意为 “纤维”,在计算机世界中则是 ”纤程“ 的意思。纤程可以看作是协程的一种,是一种任务调度方式。

JavaScript 是单线程的,有一个 event loop 的概念,它有一个有优先级的任务队列,只能按顺序执行一个任务,是不支持多个任务同时执行的。

这种设计的好处就是不用考虑多线程导致的顺序问题,并为此做一些加锁的额外逻辑,确保执行顺序符合预期。但也因为无法使用并行能力,在 CPU 密集的场景会有性能问题, 比如一个任务耗时过长会导致其他的任务,导致用户的交互响应发生延迟。

​React 的组件更新是 CPU 密集的操作,因为它要做对比新旧虚拟 DOM 树的操作(diff,React 中 Reconcilation 负责),找出需要更新的内容(patch),通过打补丁的方式更新真实 DOM 树(React 中 Renderer 负责)。当要对比的组件树非常多时,就会发生大量的新旧节点对比,CPU 花费时间庞大,当耗时大大超过 16.6ms(一秒 60 帧的基准) 时,用户会感觉到明显的卡顿。

这一系列操作是通过递归的方式实现的,是 同步且不可中断 的。因为一旦中断,调用栈就会被销毁,中间的状态就丢失了。这种基于调用栈的实现,我们称为 Stack Reconcilation。

React 16 的一个重点工作就是优化更新组件时大量的 CPU 计算,最后选择了使用 “时间分片” 的方案,就是将原本要一次性做的工作,拆分成一个个异步任务,在浏览器空闲的时间时执行。这种新的架构称为 Fiber Reconcilation。

在 React 中,Fiber 模拟之前的递归调用,具体通过链表的方式去模拟函数的调用栈,这样就可以做到中断调用,将一个大的更新任务,拆分成小的任务,并设置优先级,在浏览器空闲的时异步执行。

FiberNode

前面我们说到使用了链表的遍历来模拟递归栈调用,其中链表的节点 React 用 FiberNode 表示。

FiberNode 其实就是虚拟 DOM,它记录了:

  1. 节点相关类型,比如 tag 表示组件类型、type 表示元素类型等。
  2. 节点的指向。
  3. 副作用相关的属性。
  4. lanes 是关于调度优先级的。
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
// Instance
this.tag = tag; // 组件类型,比如 Function/Class/Host
this.key = key; // key 唯一值,通常会在列表中使用
this.elementType = null;
this.type = null; // 元素类型,字符串或类或函数,比如 "div"/ComponentFn/Class
this.stateNode = null; // 指向真实 DOM 对象
// Fiber
this.return = null; // 父 Fiber
this.child = null; // 子 Fiber 的第一个
this.sibling = null; // 下一个兄弟节点
this.index = 0; // 在同级兄弟节点中的位置
this.ref = null;
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;
this.mode = mode;
// Effects
this.flags = NoFlags;
this.subtreeFlags = NoFlags;
this.deletions = null;
this.lanes = NoLanes;
this.childLanes = NoLanes;
this.alternate = null;
// ...
}

Fiber 通过 return 指向父 Fiber,child 指向子 Fiber 的首位、sibling 指向下一个兄弟节点。通过它们我们其实就能拿到一个完整的结构树。

对于:

function App() {
return (
<div className="app">
<span>hello</span>, Fiber
</div>
);
}

形成的 Fiber 树为:

图片

其中弧线为调用顺序。紫色为 beginWork、粉色为 completeWork。beginWork 是 “递” 的过程,而 comleteWork 则是 “归” 的过程。

为什么不用 generator 或 async/await?

generator 和 async/await 也可以做到在函数中间暂停函数执行的逻辑,将执行让出去,能做到将同步变成异步。

但 React 没有选择它们,这是因为:

  1. 具有传染性,比如一个函数用了 async,调用它的函数就要加上 async,有语法开销,此外也会有性能上的额外开销。
  2. 无法在 generator 和 async/await 中恢复一些中间状态。

具体见官方的 github issue 讨论:

https://github.com/facebook/react/issues/7942#issuecomment-254987818。

Scheduler

图片

做了时间分片,拆分了多个任务,React 就可以以此为基石,给任务设置优先级。

React 实现了一个 Scheduler(调度器)来实现任务调度执行,并单独抽离为一个单独的包,它会在浏览器有空闲的时候执行。其实浏览器也提供了一个 requestIdleCallback 的 API,支持这个能力,但兼容性实在不好,React 还是自己实现了一套。

这个 Scheduler 支持优先级,底层使用了 小顶堆,确保能高效拿到最快要过期的任务,然后执行它。

小顶堆,其实就是优先级队列。小顶堆在结构上是一个完全二叉树,但能保证每次从堆顶取出元素时,是最小的元素。

任务的 优先级 分为几种:

  1. NoPriority:无优先级。
  2. ImmediatePriority:立即执行。
  3. UserBlockingPriority:用户阻塞优先级,不执行可能会导致用户交互阻塞。
  4. NormalPriority:普通优先级。
  5. LowPriority:低优先级。
  6. IdlePriority:空闲优先级。

React 自身也有优先级,叫做 Lane,两者是不同的。

结尾

React 的架构过于宏大,今天先随便说一点吧。

总的来说,React Fiber 是在 React 16 中引入的新的架构,将原本同步不可中断的更新,变成异步可中断更新,将原本一个耗时的大任务做了时间分片,拆分成一个个小任务,在浏览器空闲的时间执行。此外添加优先级的概念,将一些重要的任务先执行,比如一些用户交互的响应函数。

一切为了更好的用户体验。

责任编辑:姜华 来源: 前端西瓜哥
相关推荐

2021-05-20 06:57:16

RabbitMQ开源消息

2022-03-23 08:31:25

LRU 算法JavaScripLFU 缓存算法

2021-07-07 07:14:48

分布式ID分布式系统

2023-05-12 08:19:12

Netty程序框架

2021-07-28 10:02:54

建造者模式代码

2021-07-14 08:24:23

TCPIP 通信协议

2021-06-30 00:20:12

Hangfire.NET平台

2021-08-11 07:02:21

npm包管理器工具

2022-12-20 08:22:42

CommitMuation

2021-11-24 08:51:32

Node.js监听函数

2021-08-02 06:34:55

Redis删除策略开源

2021-11-08 08:42:44

CentOS Supervisor运维

2021-12-15 11:52:34

GPLLinuxGNU

2021-01-29 18:41:16

JavaScript函数语法

2021-06-04 09:56:01

JavaScript 前端switch

2020-11-10 10:48:10

JavaScript属性对象

2021-07-08 06:30:03

Linux CPULinux 系统

2021-08-14 10:01:43

Python条件语句Python基础

2021-02-02 18:39:05

JavaScript

2022-02-17 08:35:59

OLTPOLAP数据仓库
点赞
收藏

51CTO技术栈公众号