作者:louiszhai,腾讯 IEG 前端开发工程师
Vue3 性能提升了 1.3~2 倍,SSR 性能提升了 2~3 倍,升级 Vue3 正是当下。
背景
原计划 2019 年发布的 Vue3,又经过一年的再次打磨,终于于去年 9 月正式发布。随后,不少 UI 组件库都积极参与适配,去年 12 月,Element-plus(Element-ui 官方升级版)也发布了 beta 版。
由于项目中用到了 Element-ui 组件,组件库未适配的情况下,不敢贸然升级 Vue3。Element-plus 发布后,又经过 1 个月的观察、测试和调研,发现 Element-plus 相对成熟(还有少量 bug,后续会讲),便开始尝试升级 Vue3。
如何升级 Vue3
有两种方案可以快速升级 Vue3:
一种是使用微前端轮子,我基于 qiankun2,搭建了 Vue3 项目基座,为了保证平稳升级,子项目继续使用 Vue2,然后不断的把子项目的页面迁移到基座项目。
另一种是,直接升级 Vue3,将项目中的 Vue2 依赖库升级到 Vue3 的最新版(当前最新版是v3.0.11),并且稍微改造 webpack 编译脚本,使之适配 Vue3。
之所以会有方案一,主要还是担心 Element-plus 不够稳定,如果有天坑,又无法绕过去,除了向饿了么团队提交 PR,微前端兜个底也是不错的应急措施。
就这样微前端方案又运行了 1 个月,部分页面已完成升级,运行良好,实践证明 Element-plus 比想象中稳定,这增加了我对于方案二的信心。考虑到还有少量业务复杂的页面,在微前端模式下,子项目的各种数据多经过一层 qiankun 的 proxy 代理,性能有损耗,影响了页面更新,于是一次性将剩余的页面全部迁移到 Vue3 项目中。
实践证明,除非比较复杂的项目,或者依赖组件库没升级等原因不适合升级外,常规情况下,升级 Vue3 都是一个不错的选择。
为什么要升级 Vue3
为什么要升级 Vue3,这是一个几乎不需要回答的问题。升级 Vue3 后,代码结构更加清晰内聚,响应式数据流更加可控,节省了很多心智成本,从而使得开发效率大幅提升。Vue3 还带来了很多新特性,框架层面运行性能更高(性能提升了 1.3 至 2 倍,SSR 性能提升了 2 至 3 倍),Composition API 使得代码拆分,函数封装更容易,复杂项目也随之更容易管理。
Vue2 中,相关的逻辑经常分散在 option 的 data、watch、computed、created、mounted 等钩子中,阅读一段代码,经常需要上下反复横跳,带来了部分阅读障碍。钩子又依赖 Vue 实例,代码封装基于天生携带钩子的 Mixin 去做,更加容易和相对方便。
但正因为如此,Mixin 的钩子容易不自觉的越界,插手到页面或组件的内部变量和方法管理过程中;甚至,多个不同的 Mixin,相互之间就很容易冲突,项目开发者,在引入 Mixin 和避免冲突之间需要保持微妙的平衡,不但增加心智负担,还带来了副产品:本身扑朔迷离的 this 变得更加不确定。因此,大型项目 Mixin 几乎都是一种反模式。
现在这些框架问题,都由 Vue3 的 Composition API 解决了。
Vue3 带来了哪些新特性
我们先看一些立马能感受到变化的特性。
Proxy 代理
这是一个一上手 Vue3 就能感知的变化。即使你在 Vue3 中编写 Vue2 风格的基于 option 的代码,Proxy 也是默默提供着数据响应式。
const observe = (data) => {
Object.keys(data).forEach((key) => {
const initValue = data[key];
let value = initValue;
if (typeof initValue === 'object') {
observe(initValue);
return;
}
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
console.log('visit key value =', key, value);
return value;
},
set(val) {
console.log(`[${key}]changed,old value=${value}, new value = ${val}`);
if(value !== val) {
value = val;
}
}
});
});
};
const data = {};
Array.from(new Array(100), () => "").forEach((item, i) => {
data[i] = { value: i * 2 };
});
console.time();
observe(data);
console.timeEnd(); // default: 0.225ms
data.a = { b: 1 };
data.a.b = 2;
如上所示,Vue2 的数据响应式是通过 Object.defineProperty 实现,这是一个深度遍历的过程,无论 data 中包含多少层数据,都需要全部遍历一遍。深度遍历,给对象的每个自身属性添加 defineProperty,需要不小的性能开销,同时后面新增到 this 中的属性不提供响应式监听,因此我们需要使用诸如this.$set
这种方式去添加新属性。
Proxy 就没有这个问题,如下所示。
const observe = (data) => {
return new Proxy(data, {
get(target, key, receiver) {
console.log('visit', key);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
console.log(`[${key}]changed, value = ${value}`);
Reflect.set(target, key, typeof value ===