本文根据2019年GitHub上尤大回复原文_rfcs 的讨论和自己的一些思考总结而来。
先有问题再有答案
为什么react需要fiber时间分片而vue没有?
为什么react在开发中要手动实现优化工作而vue不需要?
fiber & 时间切片
Fiber是React 中的一个核心数据结构,它代表了一个工作单元(即一个组件)
。每个 Fiber 对象都有指向其父节点、兄弟节点和子节点的链接,这使得 React 可以在 Fiber 树中自由地导航。
这种数据结构使得 React 可以实现异步和可中断的渲染过程
。当 React 开始渲染一个新的更新时,它会从根 Fiber 开始,然后遍历 Fiber 树,为每个 Fiber 创建一个或多个新的 Fiber 来表示新的状态。这个过程被称为"reconciliation"。
在 reconciliation 过程中,React 可以根据每个 Fiber 的优先级来决定是否立即处理它,或者将它推迟到稍后处理。如果有一个更高优先级的任务(如用户交互)出现,React 可以中断当前的工作,去处理那个任务,然后在有空闲时间时再回来继续之前的工作。这就是所谓的时间切片
。
时间分片
旨在让应用在 CPU 进行大量计算时也能与用户交互,但时间分片只能对大量 CPU 计算进行优化,无法优化复杂 DOM 操作, 因为要确保用户正在操作的界面是最新的
web卡顿的场景
CPU 计算量不大,但 DOM 操作非常复杂
(比如说你向页面中插入了十万个节点)。这种场景下不管你做不做时间分片,页面都会很卡。CPU 计算量非常大
。理论上时间分片在这种场景里会有较大收益,但是人机交互研究表明,除了动画场景外,大部分用户不会觉得 10 毫秒和 100 毫秒有很大区别。也就是说,时间分片只在 CPU 需要连续计算 100 毫秒以上的情况下才有较大收益。
react为什么需要?
因为在React里经常会出现CPU计算量大的场景,也就是说100毫秒以上的计算量出现的频率很高。
原因:
- Fiber 架构的复杂性导致 React 的虚拟 DOM 协调效率较低,这是系统性的问题。
- React 使用 jsx的开发方式,甚至完全兼容js的语法特性,导致其动态性过于强,不运行根本不知道vdom其结构是怎样的,是否发生了变化。所以必须在每次更新时都重新运行,做很多的计算以保证结果的正确性, 导致它的渲染效率比 template 低,因为 template 很容易做静态分析和优化。
- React Hooks 将大部分组件树的优化 API 暴露给开发者,开发者很多时候需要手动调用 useMemo 来优化渲染效率。这意味着 React 应用默认就有 render 过多的问题。更严重的是,这些优化在 React 里很难自动化,因为这些优化要求开发者正确设置依赖数组,盲目添加 useMemo 会导致应该 render 的没 render, 很不幸,大部分开发者都很懒,不会在每个地方都加上优化,因此大部分 React 应用都会有大量的没必要的 CPU 计算工作。
vue为什么没有?
对比较而言,Vue 解决了上述问题
- Vue 的架构里没有时间分片,也就没有 Fiber,因此简单了很多,这使得渲染可以更快。
- Vue 通过分析 template、简化协调过程,做了大量的 AOT 优化,性能测试结果表明大部分的 DOM 内容有 80% 属于静态内容,因此 Vue 3 的协调速度比 Svelte 快,花费的时间比 React 的 1/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 计算量太大。
另外,时间分片,或者说并发模式,给 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 算法、让很多优化变得无法实施。