最近关于即将发布的 Vue.js 的第 3 个大版本的消息越来越密集。虽然本文所讨论的内容还没有完全确定下来,但作者已经可以肯定它将是对当前(已经非常出色的)版本的大幅升级。 Vue 团队非常擅长改进框架 API。Evan You 总结了 Vue 3 的几大改进目标:
- 速度更快。
- 体积更小。
- 更易维护。
- 以原生为目标更容易。
-
让生活更轻松。
看过 RFC 和相关讨论后,我确信上述所有目标都肯定能实现。本文会带领读者浏览一些个人认为最有趣的更改内容,介绍它们的影响和潜力。
性能优化我非常重视性能,所以在探索具体的 API 之前我想谈一谈 Vue 3 的性能。可讲的东西是很多的!几乎每个角落都能找到明显的改进!
首先来看 Vue 3 的包大小。
目前,最小化和压缩过的 Vue 运行时大小约为 20kB(当前的 2.6.10 版本为 22.8kB)。Vue 3 的包估计只需一半的体积,因此只有〜10kB!
全局 API tree-shakingVue 3 带来了许多诸如更好的模块化之类的优化,而最值得一提的是 Vue 3 源代码将支持 tree-shaking。这意味着如果你不使用它的某些功能(例如keep-alive
组件或 v-show 指令),则这些功能将不会包含在你的产品包中。在当前版本中,无论我们使用 Vue 核心中的哪些功能,所有未使用的功能最终都会进入我们的生产代码,因为 Vue 实例是作为单个对象导出的,并且打包器无法检测出代码中使用了对象的哪些属性。
// Vue 2.x - whole `Vue` object is bundled for production
import Vue from 'vue'
Vue.nextTick(() => {})
const obj = Vue.observable({})
为了使全局 API 支持 tree-shaking,Vue 团队决定通过命名导出导入其中的大多数 API,以便打包器可以检测出未使用的代码并删除它们:
// Vue 3.x - only imported properties are bundled
import { nextTick, observable } from 'vue'
nextTick(() => {})
const obj = observable({})
这是一个重大变化,因为以前的全局 API 现在只能通过命名的导出才能使用。这一更改会影响: - Vue.nextTick
- Vue.observable
- Vue.version
- Vue.compile(仅限完整构建)
- Vue.set(仅在 2.x 兼容版本中,很快你就知道为什么了)
-
Vue.delete(与上同)
我们需要一段时间才能完全享受到这一功能的好处,因为它需要在一个在生态系统中的普及过程。Vue 团队将发布兼容版本,因此我们也应该能用那些使用了旧 API 的插件,代价就是影响性能。
支持 tree-shaking 的 JavaScript API 不止一个。在后台,Vue 编译器(将 Vue 模板转换为渲染函数的工具)将检测模板中使用的指令,并对其进行 tree-shaking。以下面的模板为例:
<transition>
<div v-show="ok">hello</div>
</transition>
在被 Vue 编译器处理后,代码差不多会变成下面这个样子:
import { h, Transition, applyDirectives, vShow } from 'vue'
export function render() {
return h(Transition, [
applyDirectives(h('div', 'hello'), this, [vShow, this.ok])
])
}
所有人都会从全局 API tree-shaking 中受益(尤其是我们的用户),但我认为最看重这一功能的是那些制作小型,轻量级网站并只使用 Vue 的一部分功能来开发交互的开发者,他们主要用 Vue 来替代 jQuery 之类的库。
基于代理的响应性包的大小可能会严重影响你的应用加载时间,但是包被完整下载后,它也应该快速渲染并流畅运行。
Vue 核心团队非常了解这一点,因此我们在运行时性能上也有很大的改进。
首先来看影响最大的部分——一种基于 JavaScript 代理的新的响应系统。当前版本的 Vue 响应系统是基于 Object.defineProperty 的,其存在一些局限。最常见且令人沮丧的一个限制是 Vue 无法跟踪响应对象的属性添加 / 删除。为此我们需要使用 Vue.set 和 Vue.delete 来保证响应系统的运行符合预期。有了 JS 代理后,我们终于可以摆脱这种丑陋的解决方案了。
// Adding a new property to reacitve object in Vue 2.x
Vue.set(this.myObject, key, value)
// Adding a new property to reactive object in Vue 3
this.myObject[key] = value
代理的好处可以从更快的组件初始化和修补中看出来。根据测试,现在的速度是之前的 2 倍!
这种改进尤为重要,因为 Vue 必须使用 getters/setters 来递归地遍历所有对象及其属性,并转换它们。使用代理后,这一过程就变得容易很多。
值得一提的是,使用 JS 代理后,Vue 3 会放弃对 Internet Explorer 的支持(不包括 Edge),但请放心,对于希望支持 IE 的用户来说会有一个兼容版本可用。
时间分片根据 Evan You 的推文,此功能不会包含在 Vue 3 中。
Vue 3 之后的版本还会带来另一个非常激动人心,但很少被提到的功能,那就是对时间分片的试验性支持。
打个比方来解释什么是时间分片。想象一家甜品店前排了长长的队伍,因为店里在卖镇上最好的冰淇淋。一个人买到冰淇淋后就轮到下一个,以此类推。由于某种原因,大家不知道有哪些口味可供选择。要获取这一信息,你需要直接询问出售冰淇淋的柜台售货员。
在这种情况下,我们最后可能会看到两条队伍——其中一条是想要买冰淇淋的顾客(耐心等待),另一条则是那些希望在决定是否购买冰淇淋之前了解更多口味信息的顾客。后者希望尽快获得这一信息。不幸的是,只有一位女士在卖冰淇淋,她在为“主”队伍中的所有顾客提供完服务之前不会回答任何问题。
对于还没下决定的顾客来说,这并不是最好的体验,他们中的大多数人可能会觉得等那么久并不值当。为了解决这个问题,卖冰淇淋的女士可以在每服务 2 至 3 位顾客后回答一个关于口味的问题。这样一来两条队伍中的顾客都应该会满意这个解决方案。
这正是 CPU 运行 Web 应用程序的工作机制。我们有一条“主”队列(称为“主线程”),需要完成应用的所有主要任务(脚本、渲染等),然后才能响应用户交互。对于某些页面来说,这可能会导致非常糟糕的用户体验,具体取决于 Vue 组件加载或重新渲染所需的时间。
为了让它更加可靠,最好将这一脚本运算过程“切成”小段,并在每小段执行后查看是否有用户输入要处理。这样,无论需要多少次渲染或重新渲染,应用程序都将保持响应状态。这就是在 Vue 3 未来版本中的工作机制。
Evan 用下面的图片展示了 Vue 3 中时间分片功能的例子。请注意脚本执行时间轴中的小间隙,这些间隙是用来处理用户输入的。
轻松识别为什么组件会重新渲染工具与开箱即用的性能同等重要。据此,我们会在 Vue 3 中看到一个新的生命周期 hook——renderTriggered。我们可以使用它来跟踪和消除不必要的组件重新渲染,将其与时间分片配合使用,就成为了优化运行时性能的非常强大的武器。
const Component = {
// other properties
renderTriggered (event) {
console.log(`Re-render of ` + this.$options.name + ` component`, event)
}
}
还有什么
除了上面提到的这些内容,Vue 3 加入的改进还有很多很多,但本文提到的这些更改可能是影响最大的。大多数未提及的改进将隐藏在 Vue 编译器生成的代码中,或者融入实现细节和算法中。
还有几项改进值得一提:- 输出代码将更容易针对 JavaScript 编译器优化。
- 输出代码通常会更好地优化。
-
由于改进了补丁算法,将避免不必要的父级 / 子级重新渲染。
尽管 Vue 已经称得上是目前性能最好的框架之一,但我们还是会在第三版中看到许多重大改进,特别是在包大小和运行时性能等方面。此外 Vue 3 还带来了无数细小的优化。我认为 Vue 3 非常适合现代移动优先和性能导向的 Web 开发工作。
别忘了 Vue 是唯一由社区完全驱动的主流框架。本文列出的所有更改(和其他更多更改)都以 RFC 的形式与社区一起讨论过了。你可以帮助核心团队,表达你对活跃 RFC 的意见,甚至可以提出自己的改进建议。
让我们一起使 Vue 变得更好😉
下一步计划在下一篇文章中,我们将探讨新的 Vue 3 API 将如何影响我们编写 Web 应用程序的方式。我们将研究各种 API,包括最近流行的 Composition API,并了解如何使用它来编写更好和更易维护的代码。
网友评论