为什么react需要fiber&时间分片而vue没有?听听尤大怎么说

4,861 阅读5分钟

为什么react需要fiber&时间分片而vue没有?听听尤大怎么说
多图讲解vue3快速diff算法
多图讲解Vue3的diff算法最长递增子序列实现原理
key可以重复嘛?为什么不使用index做key

本文根据2019年GitHub上尤大回复原文_rfcs 的讨论和自己的一些思考总结而来。

先有问题再有答案

  1. 为什么react需要fiber时间分片而vue没有?
  2. 为什么react在开发中要手动实现优化工作而vue不需要?

fiber & 时间切片

Fiber是React 中的一个核心数据结构,它代表了一个工作单元(即一个组件)。每个 Fiber 对象都有指向其父节点、兄弟节点和子节点的链接,这使得 React 可以在 Fiber 树中自由地导航。

这种数据结构使得 React 可以实现异步和可中断的渲染过程。当 React 开始渲染一个新的更新时,它会从根 Fiber 开始,然后遍历 Fiber 树,为每个 Fiber 创建一个或多个新的 Fiber 来表示新的状态。这个过程被称为“reconciliation”。

在 reconciliation 过程中,React 可以根据每个 Fiber 的优先级来决定是否立即处理它,或者将它推迟到稍后处理。如果有一个更高优先级的任务(如用户交互)出现,React 可以中断当前的工作,去处理那个任务,然后在有空闲时间时再回来继续之前的工作。这就是所谓的时间切片

时间分片旨在让应用在 CPU 进行大量计算时也能与用户交互,但时间分片只能对大量 CPU 计算进行优化,无法优化复杂 DOM 操作, 因为要确保用户正在操作的界面是最新的

web卡顿的场景

  1. CPU 计算量不大,但 DOM 操作非常复杂(比如说你向页面中插入了十万个节点)。这种场景下不管你做不做时间分片,页面都会很卡。
  2. CPU 计算量非常大。理论上时间分片在这种场景里会有较大收益,但是人机交互研究表明,除了动画场景外,大部分用户不会觉得 10 毫秒和 100 毫秒有很大区别。也就是说,时间分片只在 CPU 需要连续计算 100 毫秒以上的情况下才有较大收益。

react为什么需要?

因为在React里经常会出现CPU计算量大的场景,也就是说100毫秒以上的计算量出现的频率很高。
原因:

  1. Fiber 架构的复杂性导致 React 的虚拟 DOM 协调效率较低,这是系统性的问题。
  2. React 使用 jsx的开发方式,甚至完全兼容js的语法特性,导致其动态性过于强,不运行根本不知道vdom其结构是怎样的,是否发生了变化。所以必须在每次更新时都重新运行,做很多的计算以保证结果的正确性, 导致它的渲染效率比 template 低,因为 template 很容易做静态分析和优化。
  3. React Hooks 将大部分组件树的优化 API 暴露给开发者,开发者很多时候需要手动调用 useMemo 来优化渲染效率。这意味着 React 应用默认就有 render 过多的问题。更严重的是,这些优化在 React 里很难自动化,因为这些优化要求开发者正确设置依赖数组,盲目添加 useMemo 会导致应该 render 的没 render, 很不幸,大部分开发者都很懒,不会在每个地方都加上优化,因此大部分 React 应用都会有大量的没必要的 CPU 计算工作。

vue为什么没有?

对比较而言,Vue 解决了上述问题

  1. Vue 的架构里没有时间分片,也就没有 Fiber,因此简单了很多,这使得渲染可以更快。
  2. Vue 通过分析 template、简化协调过程,做了大量的 AOT 优化,性能测试结果表明大部分的 DOM 内容有 80% 属于静态内容,因此 Vue 3 的协调速度比 Svelte 快,花费的时间比 React 的 1/10 还少。
  3. 通过数据响应式追踪,Vue 可以做到组件树级别的优化,比如把插槽编译为函数以避免 children 的变化引发 re-render,比如自动缓存内联事件处理函数以避免 re-render。Vue 3 可以做到在不借助开发者的任何手动优化的情况下,防止子组件在非必要的情况下 re-render。这意味着同样一次更新,React 应用可能要 re-render 多个组件,而 Vue 应用很可能只 re-render 一个组件。

总结

因此,在默认情况下,Vue 3 应用会比 React 应用少花费很多 CPU 时间,因而遇到 CPU 连续计算时间超过 100 毫秒的机会相当少,除非是极端情况。但大部分极端情况是 DOM 操作过于复杂,而不是 CPU 计算量太大。

另外,时间分片,或者说并发模式,给 React 带来了另外一个问题:React 需要对所有更新任务进行调度和调和,这导致 React 还需要搞定任务优先级、任务失效处理、re-entry 等任务,这会使 React 变得更复杂,进而让源码的体积膨胀。就算 React 把 Suspense、Tree-shaking 等优化都加上,Vue 3 的运行时体积也只有 React + ReactDOM 的 1/4。

注意我并不是说并发模式是个馊主意,并发模式确实对处理某些问题提供了有意思的新途径(尤其是在协调异步状态转换时 coordinating async state transitions),但是为此而实现时间分片是否值得,还需要再三权衡,至少现在不值得 Vue 3 这样做。

补充

确实时间分片解决的问题并不多,只解决了很少一部分场景的问题,比如动画和可视化。99% 的场景不需要时间分片,时间分片只会延长整个渲染时长。

Fiber 的链表遍历制约了 React 的 diff 算法、让很多优化变得无法实施。