预热
- 在 React 16 这个版本中,React 团队正式的实现了 React Fiber 架构,这全新的架构是对于老版本的 React 的核心算法的完全重构。
概念
首先我们稍微回顾一下几个概念:
虚拟 DOM(Virtual DOM):更多的是一个编程的概念,所谓的虚拟就是把真实的 UI DOM 通过某种方式先保存在内存中,有点像是双缓冲,然后通常先修改内存中虚拟 DOM,然后再把虚拟 DOM 的更改同步到真实的 DOM 元素中。
元素(Element): 用来描述真实 DOM 元素或者 React 组件实例以及其所需属性的对象。
组件元素:我们平时用 Class 或者 Function 写的一个个的 React 组件
DOM 元素:HTML 自带的一些 DOM 元素,比如 div,a,input,p 等等
比如下面这段代码,描述了一个我们自己定义的 Form组件 MyForm,里面包含了一个真实 DOM 元素 input 元素。
1 | { |
问题
问题就出在这一整个从渲染、比对树结构 到 提交DOM变更的一系列操作是不停止的,在大型应用中,会触发大量的更新运行过长的时间,导致浏览器失帧,从而带来非常糟糕的用户体验,也就会用户感受一种卡顿的感受。而 React Fiber 就是需要解决这个问题?非常直观的,可以想到:
1.许多的更新操作,中间的计算操作,我们并不需要及时的做完、及时的反应,而是只要在空闲的做就可以了,比如屏幕外组件的更新等等。
2.不同的操作之间其实是有优先级的,比如动画、用户交互(输入文字、点击等等),这些用户直观的感受的东西是需要及时的处理和反应的,动画需要保持60fps,输入文字需要马上出现等等。
知道问题所在以及我们直观的感受,就能够理解需要解决的问题是什么了。如果能够通过某种方式可以调度这些操作,把操作按优先级排列,在有限的帧时间内,执行优先级高的操作,时间不够,那么就把其他操作在放到下一帧去执行,保证连贯性。岂不妙哉 ~
其实 React Fiber 就是做了这么一个操作分解和优先级调度的事情,讲起来容易,做起来难啊!React 团队从提出 Fiber 架构到实现花了至少2年时间,可见这个问题,好理解,但是真正实现起来真的是不容易啊!!
以下乃个人浅见,来详细阐述下 React Fiber 架构具体是怎么样?是如何实现的?React Fiber 所要实现的不是让 React 更快,而是让用户体验更加的丝般顺滑。
React Fiber
为了能够调度 React 的操作,那么首先必须要想一办法能够把各种操作分解为一个个小的单元方便调度,首先要有能够调度的对象。而 Fiber 就代表了一个单元的工作,这是 React 中对于调度工作的抽象。每个 Fiber 都对应一个组件所要做的工作(完成或者未完成的),因此一个组件可能会有多个 Fiber 去完成这个组件渲染需要完成的工作的。
在 React 框架工作的时候,不管是老的算法还是新的 React Fiber 架构都是分为两个阶段。
1.Render/Reconcilition:在这个阶段呢,React 在内存中做计算,主要做以下一些事情:寻找 Element Tree 的更新点,并转换成合并为单次 DOM操作。
2.Commit: 这个阶段主要是把上个阶段生成的 DOM 操作去真正的执行。
这两个阶段也定义了,在 React 中我们所谓的渲染只是在内存中的计算,而第二阶段才是真正的应用到 DOM 元素中。React Fiber 架构是对第一阶段的一种重构实现,老的算法和新的算法的的区别就是在这个阶段。
老的算法的执行是:Render -> 只要出发就不停的一大堆计算 -> Commit 提交
新的算法的执行是:Render -> 一个个小的工作单元 | —-> 异步执行完成后 -> Commit提交
| —> 异步分散在不同的帧中执行
React Fiber 架构实现了对于第一阶段渲染阶段的异步优先级执行,把一大堆本来不能停止的计算,分解成一个个小的工作单元,并且可以控制其什么时候优先执行、停止。每一个工作单位称之为 Fiber,这里是 React 的一种提法,下面我们看看什么是 Fiber。
Fiber
在代码中,Fiber 是 React 定义的一种数据结构,这个数据结构是原有 React 的元素数据结构的升级版,它包含了每个 Fiber 对应的元素的信息、该元素的更新操作队列、类型等。
React 运行时存在 3 种实例:
1 | DOM 真实DOM节点 |
Fiber 之前的 Reconciler(被称为Stack reconciler)自顶向下的递归 mount/update,无法中断(持续占用主线程),这样主线程上的布局、动画等周期性任务以及交互响应就无法立即得到处理,有时会影响用户体验。
Fiber 解决这个问题的思路是把渲染/更新过程(递归diff)拆分成一系列小任务,每次检查树上的一小部分,做完看是否还有时间继续下一个任务,有的话继续,没有的话把自己挂起,主线程不忙的时候再继续.
增量更新需要更多的上下文信息,之前的 vDOM tree 显然难以满足,所以扩展出了 fiber tree(即 Fiber 上下文的 vDOM tree),更新过程就是根据输入数据以及现有的 fiber tree 构造出新的 fiber tree(workInProgress tree)。运行实例变成了:
1 | DOM |
Fiber 执行队列
Fiber 的结构中有一个 alternate 字段存放着正在工作的 Fiber,两者都有一个更新队列,每一个更新对同时入列到两个队列中。下面这个例子摘自React的源码。
Current Fiber Update Queue: A1 B2 C1 D2 E4 F5
Work-In-Progress Update Queue: E4 F5
大多时候,工作中的更新队列会比当前队列的更新少,表示有些更新(A B C D)已经完成。那么ABCD完成的顺序并非是按顺序来的。
比如,当前的状态是 ‘ ‘,表示空,现在是上述四个更新
A1 - B2 - C1 - D2
其中数字表示优先级,字母表示更新的状态。
第一轮更新的时候,优先级是1:
初始状态:’ ‘
[A1, C1] 完成,
最终的状态是AC。
第二轮更新的时候,优先级是2:
初始状态:A。这里是比较tricky的地方,由于第一次更新把B2给漏掉了,那么第二轮更新的时候,会重新从B2开始C1。所以在新版中,同一个更新可能会被执行两遍,不影响最终状态。
[B2, C1, D2] 完成
最终状态是 ABCD
React是按照顺序遍历的,有任何的更新跳过,在下一轮中都会重新执行一遍,虽然会多次执行,但是最后是保证最终的状态一致性的。
Updater
...
执行流程
...
更新
...
结语
通过网上的一些文章,React 团队的视频演讲,再加上对于源码的阅读,以上是自己的一个总结和思考理解的过程。
理解 React Fiber 的过程,其实已经是对 React 整个框架有了一个全面的了解了,React Fiber 作为新版 React 的核心部分,比起老版的实现是完全不同,试一次全面的升级版。了解 React Fiber 的过程让我对 React 这个框架有了更深的理解,也会对自己写 React 前端代码的时候起到指导意义。
总的来说算是对于新版 React 核心机制的自己的一个总结吧,我觉得新版的 React 实现不是一天两天能够完全去消化的,React 团队花费多年的心血,看着源码确实很多优雅的细节。整个过程不仅仅是理解 React 的实现机制,也包括前端代码的书写、工程规范、测试等等方方面面都能够学到。