1. 前言
在一系列的测试版本后,Vue3终于迎来了新版本,官方正式将其取名为Vue3 One Piece。
1.1 更好的tree-shaking
所谓tree-shaking,就是在你项目中没有用到的第三方包,没有用到的变量,没有用到的函数,没有用到的方法等等,都不会被打包进生产环境中。
与Vue 2相比,Vue 3在捆绑包大小通过tree-shaking减少了多达41%的体积,初始渲染加速多达55%,更新加快了133%,内存使用率方面表现出了显著的性能改进。
1.2 TypeScript
在Vue3中,如果你使用TypeScript,那么会拥有更好的类型推断,在Vue2.x时期其实TypeScript和Vue搭配其实并不是那么默契,Vue2.x并不能完全发挥出TypeScript的优势。而到了Vue3,Composition API可以很好地处理类型推断。
1.3 实验特性
<script setup>
:用在单一vue文件中的语法糖,因为Vue3中,几乎所有的内容都要写在setup()
中,所以官方引入了<script setup>
优化体验,具体可以参考官方文档。
<style vars>
:可以在Vue3<style>
标签中使用<script>
标签中声明的变量,或者是其它的状态,具体可以参考官方文档。
<Suspense>
:当前正在开发中的一个组件,该组件允许async setup()
在初始渲染或其它组件上等待嵌套的异步依赖项(异步组件或带有的组件),可能会在Vue3.1版本中发布。
1.4 Composition-api
说到Vue3就不得不说到composition-api,在Vue2中,提供大量的钩子函数,也就是说编写代码要按照一定的模板,比如数据就只能放在data()
中,方法只能放在methods
中,按照模板编写代码对于新手来讲可能是好事,但是一旦项目变大,维护起来就显得很困难。
下图的左边图示,即Vue2使用的Options-api,图中相同的颜色对应是组件的一种功能,可以看到为了实现一种功能,我们所写的代码是非常分散的。
如果组件逻辑复杂,代码量多,我们添加新代码不光要不停的上下滑动,而且在后期代码维护中,阅读起来也变得十分的困难,因为实现一种功能的代码并没有集中在一起。
而在Composition-api中,我们可以把实现一种功能的代码写在一起,甚至还可以把它们单独抽取在一个js
文件或者一个函数中。
在js
文件中也可以引用Composition-api的生命周期函数。这将极大的提高代码的可维护性。
import { onMounted, ref, Ref } from "vue";
function count() {
let count: Ref<number> = ref(1);
const changeCount = () => {
count.value += 2;
};
onMounted(() => {
console.log("就算不在组件模板中,也可以调用生命周期函数");
});
return {count, changeCount};
}
export default {
name: "App",
setup() {
return {
// 通过展开语法,可以直接返回count, changeCount
...count()
};
}
};
2. Vue2.x存在的问题
2.1 mixin
mixin,官方翻译叫做混入,也就是将组件中通用的属性和方法抽离出来,在其它的组件中进行引入。
混入— Vue.js。
也许很多人都没有使用过混入,虽然这种方式给编写组件时带来了极大的方便,但却也大大的提高了代码的维护难度,或许你自己写的代码,使用了很多混入,几周或者几个月后再次看这些代码,你可能并不知道代码中的一些属性和方法从什么地方进行调用。
而且不同的mixin中,命名空间还可能发生冲突,在项目中大量的使用混入后,其它人读到这个项目时,很可能一时半会不清楚这个变量在哪里声明过,那个方法又是在哪里调用过。
2.2 this
this这个问题,不管是1年,2年甚至10年的JavaScript使用经验,都有可能在这个地方翻车,虽然ES6时代来临后,this混乱的问题大大的改善了,但是还是不够,关于this,可以看这篇文章:Post not found: 面试/JavaScript:充满玄学的this指向,真的有点难 JavaScript:充满玄学的this指向,真的有点难。
而在Vue3中,因为几乎所有的代码都写在setup()
这个方法中,所以就不需要再通过this.
语法来进行调用。
而在Vue2.x时代,我们在script
标签中,调用data()
,computed
,methods
等一系列钩子中的属性和方法,都要通过this.xxx
语法进行调用。
3. 新特性
3.1 响应式原理
vue3.0更换了响应式的原理,在vue2.0是通过数据劫持结合发布者-订阅者模式实现的,具体可以看:
面试/手动实现Vue双向绑定。
深入响应式。
而vue3.0是通过**代理(Proxy)**实现的双向绑定,简单的说,在Vue2.x中,如果一个对象添加了一个新属性,在界面上是不会发生改变的,而在Vue3中,界面就会发生改变。
3.2 Hooks
Vue设计之初就参考了Angular和React,可以说vue是站在这两个巨人的肩膀上发展起来的,我对React的了解不多,但是去年有一件刷爆了前端的事情就是React Hooks的发布,虽然国内用react的公司远远没有使用vue的公司多(至少我是这么认为的),但是你经常浏览掘金等类似的网站,那么肯定对React Hooks这个名词不陌生。
4. 生命周期
vue3.0的生命周期的名称几乎全部修改了。
beforeCreate
-> 使用setup()
created
-> 使用setup()
beforeMount
->onBeforeMount
mounted
->onMounted
beforeUpdate
->onBeforeUpdate
updated
->onUpdated
beforeDestroy
->onBeforeUnmount
destroyed
->onUnmounted
errorCaptured
->onErrorCaptured
4.1 新增钩子函数
onRenderTracked
onRenderTriggered
两个钩子函数都接收一个 DebuggerEvent
,与 watchEffect
参数选项中的 onTrack
和 onTrigger
类似:
export default {
onRenderTriggered(e) {
debugger
// 检查哪个依赖性导致组件重新渲染
},
}
5. 开始使用
5.1 setup()
灵魂函数,作为vue3.0最重要的函数,作为在组件内使用Composition API的入口点。在Vue3中,几乎所有的方法和函数以及变量声明都写在setup()
中。
它会在组件实例被创建后,props
初始化完毕后调用。即在beforeCreate
之前被调用。
如果 setup
返回一个对象,则对象的属性将会被合并到组件模板的渲染上下文:
<template>
<div>{{ count }} {{ object.foo }}</div>
</template>
<script>
import { ref, reactive } from "vue";
export default {
setup() {
const count = ref(0);
const object = reactive({ foo: "bar" });
// 暴露给模板
return {
count,
object
};
}
};
</script>
注意 setup
返回的 ref 在模板中会自动解开,不需要写 .value
。
5.1.1 参数
props:setup()
的第一个参数为props,并且props的对象是响应式的。
export default {
props: {
name: String,
},
setup(props) {
console.log(props.name)
},
}
注意:不要结构props,一旦结构,props会失去响应性。
export default {
props: {
name: String,
},
setup({ name }) {
watchEffect(() => {
console.log(`name is: ` + name) // Will not be reactive!
})
},
}
如果引入了Eslint,一旦结构props就会报错:
并且props是不可变的,一旦尝试修改props的值,会触发警告。
context:从原来 2.x 中 this
选择性地暴露了一些 property。
const MyComponent = {
setup(props, context) {
context.attrs
context.slots
context.emit
},
}
注意:在setup()
中,不能使用this.xxx
。
5.2 reactive和ref
响应式api,与Vue2.x系列最大的区别,最开始使用的时候可能会有一点点的不习惯,如果要让属性具有响应式,就必须从vue中引入这两个方法,并且用其中的一种方法进行声明。
import { ref, reactive } from "vue";
5.2.1 reactive
接收一个普通对象然后返回该普通对象的响应式代理。
const obj = reactive({ count: 0 })
5.2.2 ref
接受一个参数值并返回一个响应式且可改变的 ref 对象。ref 对象拥有一个指向内部值的单一属性。
const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
如果传入 ref 的是一个对象,将调用 reactive
方法进行深层响应转换。
5.2.3 区别
ref
:接收一个属性,在方法中访问它的值需要使用xxx.value
。
reactive
:接收一个对象,在方法中可以直接访问。
5.3 computed
计算属性,也是变化比较大的一个,在vue2.x中,我们是这样使用计算属性。
computed:{
data(){
return this.data;
}
},
而在Vue3中,需要使用getter,setter函数。
const data = computed({
get: () => {
return d.value + 1;
},
set: (val) => {
d.value = val - 1
}
});
5.4 readonly
传入一个对象(响应式或普通)或 ref,返回一个原始对象的只读代理,注意:该代理是深层次的,内部的所有属性都是只读的。
const original = reactive({ count: 0 })
const copy = readonly(original)
watchEffect(() => {
// 依赖追踪
console.log(copy.count)
})
// original 上的修改会触发 copy 上的侦听
original.count++
// 无法修改 copy 并会被警告
copy.count++ // warning!
5.5 watchEffect
立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数。
const count = ref(0)
watchEffect(() => console.log(count.value))
// -> 打印出 0
setTimeout(() => {
count.value++
// -> 打印出 1
}, 100)
再调用一次监听函数,即可以停止监听。
const stop = watchEffect(() => {
/* ... */
})
// 之后
stop()
6. vite
关于vite,可以看这里:前端开发环境下的新工具:vite
目前vite暂时只支持Vue3,vite这玩意实在是太厉害了,估计再过一段时间,vite会在前端开发中占据一席之地。
就目前来说,vite没有什么明显的缺点,如果非要说缺点的话,就是目前vite仅仅只支持Vue3。
7. 最后
以上的内容只是Vue3的冰山一角,不过因为composition api的原因,Vue对于中大型项目不再显得那么吃力。
另外,官方文档远比本篇文章介绍的要详细,推荐直接食用官方文档。
Vue3的官方文档
composition api