Vue3之前我们会面临的许多问题:
Vue3的经历长达2-3年的筹备和研发,做了哪些事情?
我们从结果上面进行反推:
总的来说就是更小更快更友好
更小
Vue3 移除一些不常用的 API,引入 tree-shaking,可以将无用的模块‘剪辑’ 仅打包需要的,使打包的整体体积变小了
更快
主要体现在编译方面
1- diff算法的优化
2-静态的提升
3-事件监听缓存
4-SSR优化
更友好
Vue3在兼顾Vue2的options API同时又推出了composition API,大大增加了代码逻辑组织和代码复用能力。
实例如下:
import { toRefs, reactive } from 'vue';
function useMouse(){
const state = reactive({x:0,y:0});
const update = e=>{
state.x = e.pageX;
state.y = e.pageY;
}
onMounted(()=>{
window.addEventListener('mousemove',update);
})
onUnmounted(()=>{
window.removeEventListener('mousemove',update);
})
return toRefs(state);
}
我们只需要调用这个函数,即可获取 x、y 的坐标,完全不用关注实现过程
试想一下,如果很多类似的第三方库,我们只需要调用即可,不必关注实现过程,
开发效率大大提高
同时,VUE3 是基于 typescipt 编写的,可以享受到自动的类型定义提示
Vue3从很多层面都做了优化,可以分为三个方面:
1-源码
2-性能
3-语法API
1-源码
源码可以从两个层面展开:源码管理;Tyscript
源码管理:
首先,源码的优化体现在代码管理方式上。Vue.js 2.x 的源码托管在 src 目录,然后依据功能拆分出了 compiler(模板编译的相关代码)、core(与平台无关的通用运行时代码)、platforms(平台专有代码)、server(服务端渲染的相关代码)、sfc(.vue 单文件解析相关代码)、shared(共享工具代码) 等目录:
而到了 Vue.js 3.0 ,整个源码是通过 monorepo 的方式维护的,根据功能将不同的模块拆分到packages 目录下面不同的子目录中:
这样使得模块拆分更细化,职责划分更明确,模块之间的依赖关系也更加明确,开
发人员也更容易阅读、理解和更改所有模块源码,提高代码的可维护性
另外一些 package(比如 reactivity 响应式库)是可以独立于 Vue 使用的,这样
用户如果只想使用 Vue3 的响应式能力,可以单独依赖这个响应式库而不用去依赖
整个 Vue
TypeScript
Vue3 是基于 typeScript 编写的,提供了更好的类型检查,能支持复杂的类型推导
2-性能
vue3 是从什么哪些方面对性能进行进一步优化呢?
这里我们讲数据劫持
在vue2中,数据劫持是通过Object.defineProperty,这个API有一些缺陷,并不能检测对象属性的添加和删除
Object.defineProperty(data, 'a',{
get(){
// track
},
set(){
// trigger
}
})
尽管 Vue 为了解决这个问题提供了 set 和 delete 实例方法,但是对于用户来说,
还是增加了一定的心智负担
同时在面对嵌套层级比较深的情况下,就存在性能问题
default {
data: {
a: {
b: {
c: {
d: 1
}
}
}
}
}
相比之下,vue3 是通过 proxy 监听整个对象,那么对于删除还是监听当然也能监
听到
同时 Proxy 并不能监听到内部深层次的对象变化,而 Vue3 的处理方式是在 getter
中去递归响应式,这样的好处是真正访问到的内部对象才会变成响应式,而不是无
脑递归
3-语法 API
这里当然说的就是 composition API,其两大显著的优化
优化逻辑组织
一张图,我们可以很直观地感受到 Composition API 在逻辑组织方面的优势
相同功能的代码编写在一块,而不像 options API 那样,各个功能的代码混成一
块
逻辑复用
在 vue2 中,我们是通过 mixin 实现功能混合,如果多个 mixin 混合,会存在两个
非常明显的问题:命名冲突和数据来源不清晰
而通过 composition 这种形式,可以将一些复用的代码抽离出来作为一个函数,
只要的使用的地方直接进行调用即可
实例:
在 Vue.js 2.x 中,我们通常会用 mixins 去复用逻辑,举一个鼠标位置侦听的例子,我们会编写如下函数 mousePositionMixin:
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 似乎问题不大,但是当我们一个组件混入大量不同的 mixins 的时候,会存在两个非常明显的问题:命名冲突和数据来源不清晰。
首先每个 mixin 都可以定义自己的 props、data,它们之间是无感的,所以很容易定义相同的变量,导致命名冲突。另外对组件而言,如果模板中使用不在当前组件中定义的变量,那么就会不太容易知道这些变量在哪里定义的,这就是数据来源不清晰。但是Vue.js 3.0 设计的 Composition API,就很好地帮助我们解决了 mixins 的这两个问题。
我们来看一下在 Vue.js 3.0 中如何书写这个示例
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 函数,也不会出现命名冲突的问题。
Composition API 除了在逻辑复用方面有优势,也会有更好的类型支持,因为它们都是一些函数,在调用函数时,自然所有的类型就被推导出来了,不像 Options API 所有的东西使用 this。另外,Composition API 对 tree-shaking 友好,代码也更容易压缩。
虽然 Composition API 有诸多优势,它也不是一点缺点都没有,关于它的具体用法和设计原理,我们会在后续的章节详细说明。这里还需要说明的是,Composition API 属于 API 的增强,它并不是 Vue.js 3.0 组件开发的范式,如果你的组件足够简单,你还是可以使用 Options API。