- Published on
React Fiber 上
为什么需要 Fiber
在浏览器中,页面是一帧一帧绘制出来的,渲染的帧率与设备的刷新率保持一致。一般情况下,设备的屏幕刷新率为 1s 60 次,当每秒内绘制的帧数(FPS)超过 60 时,页面渲染是流畅的;而当 FPS 小于 60 时,会出现一定程度的卡顿现象。看完整的一帧中,具体做了哪些事情:

- 首先需要处理输入事件,能够让用户得到最早的反馈
- 接下来是处理定时器,需要检查定时器是否到时间,并执行对应的回调
- 接下来处理
Begin Frame
(开始帧),即每一帧的事件,包括window.resize
、scroll
、media query
change
等 - 接下来执行请求动画帧
requestAnimationFrame
(rAF),即在每次绘制之前,会执行 rAF 回调 - 紧接着进行
Layout
操作,包括计算布局和更新布局,即这个元素的样式是怎样的,它应该在页面如何展示 - 接着进行
Paint
操作,得到树中每个节点的尺寸与位置等信息,浏览器针对每个元素进行内容填充 - 到这时以上的六个阶段都已经完成了,接下来处于空闲阶段(Idle Peroid),可以在这时执行
requestIdleCallback
里注册的任务
js 引擎和页面渲染引擎是在同一个渲染线程之内,两者是互斥关系。如果在某个阶段执行任务特别长,例如在定时器阶段或 Begin Frame
阶段执行时间非常长,时间已经明显超过了 16ms
,那么就会阻塞页面的渲染,从而出现卡顿现象。
在 react16
引入 Fiber 架构之前,react 会采用递归对比虚拟 DOM 树,找出需要变动的节点,然后同步更新它们,这个过程 react 称为 reconcilation
(协调)。在 reconcilation
期间,react 会一直占用浏览器资源,会导致用户触发的事件得不到响应。
递归调用,执行栈会越来越深,而且不能中断,中断后就不能恢复了。递归如果非常深,就会十分卡顿。如果递归花了 100ms,则这 100ms 浏览器是无法响应的,代码执行时间越长卡顿越明显。传统的方法存在不能中断和执行栈太深的问题。
因此,为了解决以上的痛点问题,React 希望能够彻底解决主线程长时间占用问题,于是引入了 Fiber 来改变这种不可控的现状,把渲染/更新过程拆分为一个个小块的任务,通过合理的调度机制来调控时间,指定任务执行的时机,从而降低页面卡顿的概率,提升页面交互体验。通过 Fiber 架构,让 reconcilation
过程变得可被中断。适时地让出 CPU 执行权,可以让浏览器及时地响应用户的交互。 React16 中使用了 Fiber,但是 Vue 是没有 Fiber 的,为什么呢?原因是二者的优化思路不一样:
Vue 是基于 template 和 watcher 的组件级更新,把每个更新任务分割得足够小,不需要使用到 Fiber 架构,将任务进行更细粒度的拆分 React 是不管在哪里调用 setState,都是从根节点开始更新的,更新任务还是很大,需要使用到 Fiber 将大任务分割为多个小任务,可以中断和恢复,不阻塞主进程执行高优先级的任务。
什么是 Fiber
Fiber 可以理解为是一个执行单元,也可以理解为是一种数据结构。
一个执行单元
Fiber 可以理解为一个执行单元,每次执行完一个执行单元,react 就会检查现在还剩多少时间,如果没有时间则将控制权让出去。React Fiber 与浏览器的核心交互流程如下:

首先 React 向浏览器请求调度,浏览器在一帧中如果还有空闲时间,会去判断是否存在待执行任务,不存在就直接将控制权交给浏览器,如果存在就会执行对应的任务,执行完成后会判断是否还有时间,有时间且有待执行任务则会继续执行下一个任务,否则就会将控制权交给浏览器。这里会有点绕,可以结合上述的图进行理解。
Fiber 可以被理解为划分一个个更小的执行单元,它是把一个大任务拆分为了很多个小块任务,一个小块任务的执行必须是一次完成的,不能出现暂停,但是一个小块任务执行完后可以移交控制权给浏览器去响应用户,从而不用像之前一样要等那个大任务一直执行完成再去响应用户。