# 2.React-Fiber
# 对 React-Fiber 的理解,它解决了什么问题?
React V15 在渲染时,会递归比对 VirtualDOM 树,找出需要变动的节点,然后同步更新它们, 一气呵成。这个过程期间, React 会占据浏览器资源,这会导致用户触发的事件得不到响应,并且会导致掉帧,导致用户感觉到卡顿。 为了给用户制造一种应用很快的“假象”,不能让一个任务长期霸占着资源。 可以将浏览器的渲染、布局、绘制、资源加载(例如 HTML 解析)、事件响应、脚本执行视作操作系统的“进程”,需要通过某些调度策略合理地分配 CPU 资源,从而提高浏览器的用户响应速率, 同时兼顾任务执行效率。 所以 React 通过 Fiber 架构,让这个执行过程变成可被中断。“适时”地让出 CPU 执行权,除了可以让浏览器及时地响应用户的交互,还有其他好处:
分批延时对 DOM 进行操作,避免一次性操作大量 DOM 节点,可以得到更好的用户体验; 给浏览器一点喘息的机会,它会对代码进行编译优化(JIT)及进行热代码优化,或者对 reflow 进行修正。
核心思想: Fiber 也称协程或者纤程。它和线程并不一样,协程本身是没有并发或者并行能力的(需要配合线程),它只是一种控制流程的让出机制。让出 CPU 的执行权,让 CPU 能在这段时间执行其他的操作。渲染的过程可以被中断,可以将控制权交回浏览器,让位给高优先级的任务,浏览器空闲后再恢复渲染。
# 问题怎么引起的
我们先简单看下 v15 架构,分为两层:
- Reconciler(协调器)—— 负责进行 Diff 运算,调用组件生命周期方法等
- Renderer(渲染器)——负责将变化的组件渲染到页面上(分平台主要有 ReactDOM、ReactNative)
每当有更新发生时,Reconciler 会做如下工作:
- 调用函数组件的 render 方法,将返回的 JSX 转化为 Virtual DOM
- 将 Virtual DOM 和上次更新时的 Virtual DOM 进行对比
- 通过 Diff 找出差异
- 通知 Renderer,将变化的 Virtual DOM 渲染到页面上
其中,在 React v15 中,reconciler 是不能中途被打断的(Stack Reconciler),需要将递归调用的堆栈挨个执行完,直至栈空。这样的话,当组件树像上面 Cartoon 演示那样,层级很深、庞大到一定程度,且在不断更新组件状态的时候,就有可能出现掉帧的现象。 我们来看这两个关键点:
- stack reconciler 不能中途被打断
- 浏览器为什么会出现掉帧。(浏览器每秒 60 刷,在 16ms 没有渲染的情况下,肉眼可以看到卡顿)
# 什么是 Fiber
Fiber 其实是一个数据结构,需要记录节点和节点之间的关系。
# Fiber 能实现什么?
- pause work and come back to it later(暂停工作,并且能之后回到暂停的地方)
- assign priority to different types of work(安排不同类型工作的优先级)
- reuse previously completed work(之前已经处理完的工作单元,可以得到重用)
- abort work if it’s no longer needed(如果后续的工作不再需要做,工作可以直接被终止)
那么这四个功能如何实现呢,这里需要看一下Structure of Fiber
和 General algorithm
两大模块。
# 那么为什么有 React-Fiber,没有 Vue-Fiber 呢?
详情看 为什么没有 Vue-Fiber? (opens new window)
主要原因:
上文提到修改数据时,react 需要调用 setState 方法,而 vue 直接修改变量就行。看起来只是两个框架的用法不同罢了,但响应式原理正在于此。
从底层实现来看修改数据:在 react 中,组件的状态是不能被修改的,setState 没有修改原来那块内存中的变量,而是去新开辟一块内存;而 vue 则是直接修改保存状态的那块原始内存。
所以经常能看到 react 相关的文章里经常会出现一个词"immutable",翻译过来就是不可变的。
数据修改了,接下来要解决视图的更新:react 中,调用 setState 方法后,会自顶向下重新渲染组件,自顶向下的含义是,该组件以及它的子组件全部需要渲染;而 vue 使用 Object.defineProperty(vue@3 迁移到了 Proxy)对数据的设置(setter)和获取(getter)做了劫持,也就是说,vue 能准确知道视图模版中哪一块用到了这个数据,并且在这个数据修改时,告诉这个视图,你需要重新渲染了。
所以当一个数据改变,react 的组件渲染是很消耗性能的——父组件的状态更新了,所有的子组件得跟着一起渲染,它不能像 vue 一样,精确到当前组件的粒度。
上面说了这么多,都是为了方便讲清楚为什么需要 react fiber:在数据更新时,react 生成了一棵更大的虚拟 dom 树,给第二步的 diff 带来了很大压力——我们想找到真正变化的部分,这需要花费更长的时间。js 占据主线程去做比较,渲染线程便无法做其他工作,用户的交互得不到响应,所以便出现了 react fiber。
总结:
- react 因为先天的不足——无法精确更新,所以需要 react fiber 把组件渲染工作切片;而 vue 基于数据劫持,更新粒度很小,没有这个压力;
- react fiber 这种数据结构使得节点可以回溯到其父节点,只要保留下中断的节点索引,就可以恢复之前的工作进度;