Description
源码优化
1. 新的代码管理方式 Monorepo
什么是 Monorepo 呢?简单来说,指在一个项目仓库 (repo) 中管理多个模块/包 (package),不同于常见的每个模块建一个 repo。
Vue2.x的源码托管于src目录里面,根据功能划分出compiler(模板编译),core(与平台无关的通用代码),platforms(平台专有代码)、server(服务端渲染代码)、sfc(vue单文件解析代码)、shared(共享工具代码)等。
到了 Vue.js 3.0 ,整个源码是通过 monorepo 的方式维护的,根据功能将不同的模块拆分到 packages 目录下面不同的子目录中
2. 采用了 TypeScript 开发
对于复杂的框架项目开发,使用类型语言非常有利于代码的维护,因为它可以在编码期间帮你做类型检查,避免一些因类型问题导致的错误。
Vue.js 2.x 使用的类型语言是 Flow,但是 Flow 对于一些复杂场景类型的检查,支持得并不好。所以 Vue.js 3.0 使用 TypeScript 重构了整个项目。
性能优化
1. 源码体积优化
Vue.js 3.0 在源码体积的减少方面做了一些工作:
- 移除了一些冷门的 feature(比如 filter、inline-template 等)
- 引入 tree-shaking 的技术,减少打包体积
tree-shaking 就是打包的时候自动移除未引用的模块,去除冗余代码。
利用 tree-shaking 技术,如果你在项目中没有引入 Transition、KeepAlive 等组件,那么它们对应的代码就不会打包,这样也就间接达到了减少项目引入的 Vue.js 包体积的目的。
2. 数据劫持优化
Vue.js 区别于 React 的一大特色是它的数据是响应式的,DOM 是数据的一种映射,数据发生变化后可以自动更新 DOM。
要实现这个功能,必须得劫持数据的访问和更新,Vue.js 1.x 和 Vue.js 2.x都是通过 Object.defineProperty 这个API去劫持数据的getter和setter的
Object.defineProperty(data, 'a', {
get(){
// track
},
set(){
// trigger
}
})
它的缺陷主要是:
- Object.defineProperty 的缺陷在于必须事先知道要劫持的属性是什么,所以不能动态检测到对象属性添加与删除。虽然Vue提供了 $set和 $delete 实例方法,但是增加了心智负担。
- 无法监听数组的变化,Vue内部实现是使用了一些hack,把无法监听数组的情况通过重写数组的部分方法来实现响应式,只是拦截了unshift shift push pop splice sort reverse 这几个方法,直接通过数组下标修改数组是无法监听到的。
- 另外 Object.defineProperty 无法判断运行时具体访问哪个属性,所以对于嵌套层级较深的对象,需要递归遍历把该对象的每一层数据都变成响应式的,这无疑会有相当大的性能压力。
为了解决这些缺陷,Vue.js 3.0 使用了ES6新特性 Proxy做数据劫持,它的内部是这样的
observed = new Proxy(data, {
get() {
// track
},
set() {
// trigger
}
})
Proxy 可以劫持整个对象,并返回一个新对象。但要注意的是,Proxy API并不能监听到内部深层次对象的变化,Vue.js 3.0 的处理方式是在 getter 中去递归响应式,这样的好处是真正访问到的内部对象才会变成响应式,提高了性能。
语法 API 优化:Composition API
Vue.js 3.0 在语法方面进行了优化,主要是提供了 Composition API。
1. 优化逻辑组织
Vue以前的版本中,数据和逻辑被分散在了各个option中,当组件小的时候,这种分类方式一目了然;但是在大型组件中,一个组件可能有多个逻辑关注点,当使用 Options API 的时候,每一个关注点都有自己的 Options,如果需要修改一个逻辑点关注点,就需要在单个文件中不断上下切换和寻找。
Vue.js 3.0 提供了一种新的 API:Composition API,它有一个很好的机制去解决这样的问题,就是将某个逻辑关注点相关的代码全都放在一个函数里,这样当需要修改一个功能时,就不再需要在文件中跳来跳去
- 优化逻辑复用
Vue2.x js中,我们经常会用mixins去复用逻辑,例如鼠标位置监听的逻辑,编写一个mixin
const mousePositionMixin = {
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
}
}
}
export default mousePositionMixin
组件中使用:
<template>
<div>
Mouse position: x {{ x }} / y {{ y }}
</div>
</template>
<script>
import mousePositionMixin from './mouse'
export default {
mixins: [mousePositionMixin]
}
</script>
使用单个mixin基本是没有问题的,但是如果一个复杂组件内引用了多个mixin时,经常会造成命名冲突,并且数据源来源不清晰,类似于React的HOC高阶组件的Props。但是Vue.js 3.0 设计的 Composition API,就很好地帮助我们解决了 mixins 的这两个问题。
例如:
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 }
}
我们约定 useMousePosition 这个函数为 hook 函数,然后在组件中使用:
<template>
<div>
Mouse position: x {{ x }} / y {{ y }}
</div>
</template>
<script>
import useMousePosition from './mouse'
export default {
setup() {
const { x, y } = useMousePosition()
return { x, y }
}
}
</script>
可以看到,整个数据来源清晰了,即使去编写更多的 hook 函数,也不会出现命名冲突的问题。