Stack Reconciler(栈协调器)
在了解Fiber架构之前,需要对React15的Stack Reconciler(栈协调器)做一次完整了解。先了解一下什么Reconciler协调器,在React中是这么定义的:
Virtual DOM 是一种编程概念。在这个概念里,UI 以一种理想化的,或者说“虚拟的”表现形式被保存于内存中,并通过如 ReactDOM 等类库使之与“真实的” DOM 同步。这一过程叫作Reconciler 协调(调和)。
所以实现Reconciler,其实就是实现虚拟DOM到真实DOM渲染的整个逻辑过程,因此调和 !== Diff,但是Diff 确实是调和过程中最具代表性的一环。
那么要了解React15是如何实现Stack Reconciler,最重要的两块:
- Diff算法策略
- 找到diff节点并
同步
更新渲染
Diff算法策略要点:(主要是树递归)
1.Diff 算法性能突破的关键点在于“分层对比”;
2.类型一致的节点才有继续 Diff 的必要性;
3.key 属性的设置,可以帮我们尽可能重用同一层级内的节点。
Fiber架构
我们先来看看 React 团队在“React 哲学”中对 React 的定位:
我们认为,React 是用 JavaScript 构建快速响应的大型 Web 应用程序的首选方式。它在 Facebook 和 Instagram 上表现优秀。
快速响应
是React哲学理念,因此Fiber架构的出现是为了让React框架能更加快速响应用户的操作。
是什么
什么是 Fiber?从字面上来理解,Fiber 这个单词翻译过来是“丝、纤维”的意思,是比线还要细的东西。在计算机科学里,我们有进程、线程之分,而 Fiber 就是比线程还要纤细的一个过程,也就是所谓的“纤程”。纤程的出现,意在对渲染过程实现更加精细的控制。
Fiber的概念理解:
- 从架构角度来看,Fiber 是对 React 核心算法(即调和过程)的重写
- 从编码角度来看,Fiber 是 React 内部所定义的一种数据结构,它是 Fiber 树结构的节点单位,也就是 React 16 新架构下的“虚拟 DOM”;
- 从工作流的角度来看,Fiber 节点保存了组件需要更新的状态和副作用,一个 Fiber 同时也对应着一个工作单元。
从架构角度理解Fiber:
- 架构核心:“可中断”“可恢复”与“优先级”
- 可中断,指的是在Fiber架构下,任何工作任务都可以被更高优先级的任务中断
- 可恢复,指的是被中断的任务可以被恢复继续执行
- 优先级,指的是每个任务都有自己的优先级定义
- 因此需要增加“Scheduler(调度器)”,作用是调度更新的优先级的任务
怎么解决问题
有了Fiber架构,怎么解决React15所面临的问题,虚拟DOM同步渲染真实DOM导致页面卡顿?
- 将虚拟DOM,从原有的树结构,改为链表结构,拆分成一个个Fiber树节点
- 利用Fiber架构,将渲染过程拆分成一个个工作单元任务,设置优先级,支持可中断、可恢复
- 这样子当需要渲染复杂DOM时候,同时不影响其他优先级较高工作任务执行
当然这样子讲只是简单的原理,还需要弄明白异步后可能产生更多问题?比如如何定制优先级,当两个同样优先级的任务相遇的时候如何解决,这些放在第二章讲解。
总结
经过第一篇Fiber文章学习,大概了解到Fiber架构出现的背景和原因,以及它是什么,是如何工作的解决之前所遇到问题。简单总结一下:
- React中定义Reconciler协调,指的是将虚拟DOM渲染到真实DOM的过程,React15之前采用是stack Reconciler栈协调,同步渲染机制导致页面卡顿
- React16之后采用Fiber Reconciler,实现异步渲染DOM
- 采用新的Fiber架构,同时影响到React的整个生命周期,主要是在更新阶段的生命周期
额外话题(Vue.js对比)
相比较React做Fiber架构优化,主要是针对事件做了时间分片,那么为什么Vue3(Vue@next)版本并不需要做呢?Vue.js作者尤雨溪是这样子回答的:
尤雨溪:在 Web 应用中,「可中断式更新」主要是由大量 CPU 计算加上复杂 DOM 操作引起的。时间分片旨在让应用在 CPU 进行大量计算时也能与用户交互,但时间分片只能对大量 CPU 计算进行优化,无法优化复杂 DOM 操作,因为要确保用户正在操作的界面是最新的状态才行。因此,我们可以考虑两种不同的可中断式更新的场景:
- 1.CPU 计算量不大,但 DOM 操作非常复杂(比如说你向页面中插入了十万个节点)。这种场景下不管你做不做时间分片,页面都会很卡。
- 2.CPU 计算量非常大。理论上时间分片在这种场景里会有较大收益,但是人机交互研究表明,除了动画之外,大部分用户不会觉得 10 毫秒和 100 毫秒有很大区别。 也就是说,时间分片只在 CPU 需要连续计算 100 毫秒以上的情况下才有较大收益。有意思的地方就出现了,在 React 经常会出现 100 毫秒以上的计算量,因为
- 3.Fiber 架构的复杂性导致 React 的虚拟 DOM 协调效率较低,这是系统性的问题。
- 4.React 使用 JSX 导致它的渲染效率比 template 低,因为 template 很容易做静态分析和优化。
- 5.React Hooks 将大部分组件树的优化 API 暴露给开发者,开发者很多时候需要手动调用 useMemo 来优化渲染效率。这意味着 React 应用默认就有 render 过多的问题。更严重的是,这些优化在 React 里很难自动化。
- 6.这些优化要求开发者正确设置依赖数组
- 7.盲目添加 useMemo 会导致应该 render 的没 render。 很不幸,大部分开发者都很懒,不会在每个地方都加上优化,因此大部分 React 应用都会有大量的没必要的 CPU 计算工作。 对比较而言,Vue 解决了上述问题:
- 8.Vue 的架构里没有时间分片,也就没有 Fiber,因此简单了很多,这使得渲染可以更快。
- 9.Vue 通过分析 template、简化协调过程,做了大量的 AOT 优化,性能测试结果表明大部分的 DOM 内容有 80% 属于静态内容,因此 Vue 3 的协调速度比 Svelte 快,花费的时间比 React 的 1/10 还少。
- 10.通过数据响应式追踪,Vue 可以做到组件树级别的优化,比如把插槽编译为函数以避免 children 的变化引发 re-render,比如自动缓存内联事件处理函数以避免 re-render。Vue 3 可以做到在不借助开发者的任何手动优化的情况下,防止子组件在非必要的情况下 re-render。这意味着同样一次更新,React 应用可能要 re-render 多个组件,而 Vue 应用很可能只 re-render 一个组件。 因此,在默认情况下,Vue 3 应用会比 React 应用少花费很多 CPU 时间,因而遇到 CPU 连续计算时间超过 100 毫秒的机会相当少,除非是极端情况。但大部分极端情况是 DOM 操作过于复杂,而不是 CPU 计算量太大。
进行汇总一下描述,Vue3之所以没有使用Fiber架构,主要有以下几个原因:
1.Vue.js针对template渲染机制做了多重优化,包括AOT优化(在构建的时候提前进行编译,提前将template转义成render函数)等,使得DOM元素渲染更快
2.复杂DOM渲染出现超过100ms以上的计算,是因为React本身机制导致,并不是所有复杂的DOM渲染都会需要100ms
3.React Hook的暴露增加渲染效率的复杂度,从而导致React渲染更慢,从而需要Fiber架构去协调
4.Vue数据响应式追踪机制,避免了多次重复render组件树,提高渲染效率
5.Vue使用Fiber架构去实现,确实可以有好处,但是会增加整体代码体积和复杂度,投入产出比太低
补充知识点
AOT vs JIT
- AOT,Ahead Of Time,提前编译或预编译,宿主环境获得的是编译后的代码,在浏览器中我们可以直接下载并运行编译后的代码,比如:Vue的template是通过Vue-loader编译后才能使用。
- JIT,Just In Time,即时编译 ,代码在宿主环境编译并执行,每个文件都是单独编译的,当我们更改代码时不需要再次构建整个项目,比如:React中JSX只有在浏览器运行的时候才知道具体代码。