优化
尽管Vue2已经足够优秀,但随着前端技术的发展和Vue2所面临的问题越来越多,新版的Vue3不得不做出一些优化、引入新特性,甚至是重构来解决这些问题。
新版Vue3它所做的优化包含几大方面:源码优化、性能优化、语法API优化以及引入RFC。
源码优化
源码优化体现在两个方面:使用Monorepo进行代码管理以及使用TypeScript来进行静态类型检查。
如果你对Monorepo还不是特别了解,你可以去Monorepo章节去了解更多。
更好的代码组织方式
在Vue3中,代码组织方式采用Monorepo来进行管理,其packages目录如下:
|-- packages
| |-- compiler-core
| |-- compiler-dom
| |-- compiler-sfc
| |-- compiler-ssr
| |-- reactivity
| |-- ref-transform
| |-- runtime-core
| |-- runtime-dom
| |-- runtime-test
| |-- server-renderer
| |-- sfc-playground
| |-- shared
| |-- size-check
| |-- template-explorer
| |-- vue
| |-- vue-compat
| |-- global.d.ts这样做的好处有很多:
- 将不同的模块拆分成单独的package包,每个包下都有各自的API、类型定义以及代码测试,使每个包职责更加明确,开发人员也更容易阅读。
- 不同的package包,可脱离
Vue单独使用,例如我们只想使用其响应式能力,则可以单独使用@vue/reactivity。
静态类型检查
在Vue2中,类型检查采用的是Flow,但在Vue3中则使用TypeScript来进行重构。
之所以选择TypeScript,有以下几个方面的原因:
TypeScript比Flow更适合用来进行复杂类型的推断。TypeScript生态越来越丰富,同时TypeScript团队一直保持着一定频率的更新和维护,相反Flow则有点烂尾。
性能优化
性能优化体现在:源码体积优化、数据劫持优化以及编译优化等。
源码体积优化
源码体积优化主要体现在两个方面:移除冷门的API、引入tree-shaking技术。
在Vue2中,由于需要照顾各个版本的开发人员的开发体验,因此不得不保留一些冷门的API,例如filter,inline-template,$on以及$off等等。而在Vue3中,它们已经从源代码中被移除了,因此也意味着源码体积越来越小。
如果你想知道哪些API被废弃了,可以点击Vue3迁移指南去了解更多。
在Vue3中,其引入了tree-shaking技术,它依赖于ES Module模块规范,当那些没有使用import引入的代码,在打包到正式环境时会被压缩工具剔除掉。例如:如果我们在项目中没有使用transition这个内置组件,那么transition相关的代码就不会被打包到我们的项目中,这在Vue2中是办不到的。
数据劫持优化
数据劫持优化体现在新版Vue采用Proxy,和Object.defineProperty()相比,它有如下几个特点:
Proxy是劫持整个对象的操作,无论是新增属性,删除属性都能被劫持到,而Object.defineProperty()无法劫持到删除属性和添加新属性,因此在Vue2中额外定义了$set()和$delete()这两个方法来弥补。- 当存在嵌套对象时,
Proxy可以做到懒劫持,例如:
const obj = {
name: '张三',
job: {
name: 'Fe'
}
}
const handler = {
get (target, key, receiver) {
const val = target[key]
if (typeof val === 'object' && val !== null) {
return new Proxy(val, handler)
}
return Reflect(target, key, receiver)
}
}
const proxy = new Proxy(obj, handler)
console.log(proxy.name) // job对象没有进行数据劫持
console.log(proxy.job) // job对象进行了数据劫持从以上代码可以看出,当真正访问到嵌套对象时,嵌套对象才会进行数据劫持,这在很大程度上提升了性能,而在Vue2中,当遇到嵌套对象时,必须递归遍历嵌套对象的所有key进行响应式处理。
- IE11浏览器不支持
Proxy,意味着如果你的项目需要兼容IE11,那么你不得不慎重选择Vue的版本。
编译优化
Vue3编译优化用一句话来总结就是:从模板大小正相关提升到与动态节点正相关。
下图是Vue完整的编译流程

从图中可以看出,影响编译性能的两大步骤主要是compile和patch阶段,其中compile到render的过程可以借助vue-loader这类插件进行离线编译,而patch的过程正是Vue3着重优化的步骤。
我们知道,Vue的最小的更新粒度是组件级别,它能最小化通知哪些组件需要更新,但在组件内部它任然需要去对比每一个节点。
<template>
<div id="content">
<p class="text">static text</p>
<p class="text">{{message}}</p>
<p class="text">static text</p>
</div>
</template>在Vue2中,虽然第一个、第三个p节点是静态节点,但依旧会去比较节点,这在很大程度上属于性能的浪费。
在Vue3中,它在编译时会对节点进行分析,将那些动态节点(第二个p节点)编译到一个Block Tree中,当下一次组件更新时只需要处理Block Tree即可。
语法API优化
Vue3中对于语法API的优化,主要归功于引入了Composition API来帮助我们更好的进行逻辑组织和逻辑复用。
优化逻辑组织
当我们使用Vue2开发项目时,更像是根据Vue2规定好的格式来撰写代码,如下:
export default {
data () {
return {
msg: 'Hello'
}
},
created () {
console.log(this.msg)
},
methods: {
say () {
console.log(this.msg)
}
},
...
}这种方式本质上没什么问题,但问题是当我们的模块相对比较复杂时,往往为了完成一个需求,不得不将这些有关联的逻辑拆散到各个option中,从而造成了一个完整的逻辑变得相对分散的场景。
当使用Composition API时,它能很好的解决以上问题,从下图可以很直观的看到效果。

优化逻辑复用
在Vue2中,当存在抽离复用逻辑时,我们会使用mixins来处理,如下:
import mousePositionMixin from './mouse.js'
export default {
mixins: [mousePositionMixin]
}当使用的mixins越来越多时,我们必须要面对两大问题:数据来源不清晰和命名冲突。
既然mixins有以上两大问题,那么Composition API又会是怎么样的呢?
还是使用相同的例子,我们来看一下使用Composition API是什么样的结果。
import useMousePosition from './mouse.js'
export default {
setup () {
const { x, y } = useMousePosition()
return { x, y }
}
}可以很直观的看出:x和y这两个变量是从useMousePosition()中引入的,这就很好的解决了数据来源不清晰地问题。
以下是mousePosition在Vue2和Vue3中不同定义方式的对比:
// vue2定义方式
export default {
data() {
return {
x: 0,
y: 0
}
},
mounted() {
window.addEventListener('mousemove', this.update)
},
destroyed() {
window.removeEventListener('mousemove', this.update)
},
methods: {
update(e) {
this.x = e.pageX
this.y = e.pageY
}
}
}
// vue3定义方式
import { ref, onMounted, onUnmounted } from 'vue'
export default function useMousePosition() {
const x = ref(0)
const y = ref(0)
const update = e => {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => {
window.addEventListener('mousemove', update)
})
onUnmounted(() => {
window.removeEventListener('mousemove', update)
})
return { x, y }
}因为mixins本质上是在this组件实例上去挂载不同的属性,所以极其容易发生命名冲突的问题,但在Composition API中,其变量定义是隔离的,这就很好的解决了命名冲突的问题。
引入RFC
RFC全称叫:Request For Comments。
在Vue2版本后期,它引入了RFC,其本质是希望一个新功能在引入框架时,应该受到社区的讨论。只有那些讨论通过的RFC,才会最终被实现并添加到框架中。这为框架的发展,提供了一个一致且可控的路径。
在Vue3新版本中,其全面启用了RFC,你可以在RFC这个链接中看到有哪些RFC正在被讨论,又有哪些RFC已经被实现合并。
