# 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 会做如下工作:

  1. 调用函数组件的 render 方法,将返回的 JSX 转化为 Virtual DOM
  2. 将 Virtual DOM 和上次更新时的 Virtual DOM 进行对比
  3. 通过 Diff 找出差异
  4. 通知 Renderer,将变化的 Virtual DOM 渲染到页面上

其中,在 React v15 中,reconciler 是不能中途被打断的(Stack Reconciler),需要将递归调用的堆栈挨个执行完,直至栈空。这样的话,当组件树像上面 Cartoon 演示那样,层级很深、庞大到一定程度,且在不断更新组件状态的时候,就有可能出现掉帧的现象。 我们来看这两个关键点:

  • stack reconciler 不能中途被打断
  • 浏览器为什么会出现掉帧。(浏览器每秒 60 刷,在 16ms 没有渲染的情况下,肉眼可以看到卡顿)

# 什么是 Fiber

Fiber 其实是一个数据结构,需要记录节点和节点之间的关系。

# Fiber 能实现什么?

  1. pause work and come back to it later(暂停工作,并且能之后回到暂停的地方)
  2. assign priority to different types of work(安排不同类型工作的优先级)
  3. reuse previously completed work(之前已经处理完的工作单元,可以得到重用)
  4. abort work if it’s no longer needed(如果后续的工作不再需要做,工作可以直接被终止)

那么这四个功能如何实现呢,这里需要看一下Structure of FiberGeneral algorithm 两大模块。

详情看: Fiber (opens new window)

# 那么为什么有 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。

总结:

  1. react 因为先天的不足——无法精确更新,所以需要 react fiber 把组件渲染工作切片;而 vue 基于数据劫持,更新粒度很小,没有这个压力;
  2. react fiber 这种数据结构使得节点可以回溯到其父节点,只要保留下中断的节点索引,就可以恢复之前的工作进度;
Last Updated: 6/3/2024, 1:08:34 AM