Vue3.0 新特性学习(一)
Vue3 出来已经有一段时间了,昨天翻了一遍官方文档,今天尝试了一下,顺便做个与 Vue2 的差异记录。
关于 Vue3 包的体积,性能上的区别我就不再多说,因为这对我们使用者来说是没得选的,性能有提升更好,没提升之前 Vue2 的版本不也照样用嘛。所以这篇文章主要说开发体验上的区别。
创建应用
我们从创建应用说起,Vue2 使用的是 new Vue
的方式创建应用,而 Vue3 使用的是调用 Vue 实例上的 Vue.createApp
方法创建。
// 2.x
const vm = new Vue({
//...options
})
vm.$mount('#app')
// 3.x
const vm = Vue.createApp({
//...options
})
vm.mount('#app')
Vue3 中一些全局的方法可以链式调用了:
const app = Vue.createApp({})
app.component('component', component)
.directive('directive', directive)
.use(plugin)
app.mount('#app')
⚠️注意别这么用:
// 错误用法
const app = Vue.createApp({}).mount('#app')
app.component('component', component)
.directive('directive', directive)
.use(plugin)
因为 moun
返回的是根组件实例,而不是应用实例。
扩展属性区别
Vue3 中 ,全局 confg 中新增了一个 globalProperties 属性,它是一个对象,对象里面的属性全局都可以访问到。
// Vue2 中在 Vue 上挂载 axios
import axios from 'axios'
Vue.prototype.$axios = axios
// Vue3 中在 Vue 上挂载 axios
const app = Vue.createApp({})
app.config.globalProperties.$axios = axios
注册组件区别
// Vue2 中注册组件
Vue.component('component-name', { ...options })
// Vue3 中注册组件
const app = Vue.createApp({})
app.component('component-name', { ...options })
Vue3 的组件注册方式解决了 Vue2 的全局组件污染问题。
<body>
<div id="app">
<test></test>
</div>
<div id="app2">
<test></test>
</div>
</body>
// Vue2
Vue.component('test',{template: '<div>test-component</div>'})
const vm = new Vue({
el: '#app',
})
const vm2 = new Vue({
el: '#app2',
})
可以看到 test
组件只注册了一次,但是 vm
与 vm2
都可以使用 test
组件。这可能会不经意导致组件名称冲突组件被覆盖。
而 Vue3 则不会出现这种情况:
const app = Vue.createApp({})
app.component('test',{template: '<div>app1</div>'})
app.mount('#app')
const app2 = Vue.createApp({})
app2.component('test',{template: '<div>app2</div>'})
app2.mount('#app2')
Vue.component
只是一个代表,其实类似这样的 API,比如Vue.directive
, Vue.mixin
都存在这样的问题。Vue3 已经全都规避掉了。
接下来其实你完全可以按照 Vue2 的习惯来编写代码,Vue3 并没有抛弃 Vue2 的写法,不过有一点需要注意的是,Vue2 和 Vue3 的生命周期钩子有所区别:
- Vue2 中的 beforeDestroy 变成了 beforeUnmount
- VUe2 中的 destroyed 变成了 unmounted
- Vue3 中新增了为了方便调试的 renderTracked 和 renderTriggered 钩子
还有个新特性,就是Vue3 终于不限制单一跟节点了!!!
组合式 API
Vue3 对开发者来说最大的区别应该就是 组合式 API 了吧(至少对我来说是的)。
setup
Vue3 中新增了一个 setup()
选项,官方文档上是这样定义它的:
一个组件选项,在创建组件之前执行,一旦
props
被解析,并作为组合式 API 的入口点
setup
接受两个参数,第一个为 props
, 第二个是 context
,props
很好理解,这个 context
包含的内容我们打印看下:
props
解析后 setup
就会被调用,那么这么说来,它应该比 beforeCreate 钩子早执行,验证一下:
// html 省略
const vm = Vue.createApp({
setup(props, context){
console.log(3)
},
beforeCreate() {
console.log(1)
},
created() {
console.log(2)
},
})
vm.mount('#app')
执行结果:
没有猜错,早于 beforeCreate 钩子执行。
在 setup 内部创建响应式数据
<body>
<div id="app">
{{v}}{{c.a}}{{obj.a}}
</div>
</body>
在 setup
内部使用或修改响应式数据,不能单纯地使用直接用变量名,而是需要用 .value
的方式取值。但是在外部使用的时候,Vue 会自动帮你展开,所以在模板或其他地方使用的时候,直接使用变量就行了。
const options = {
setup(){
// 创建响应式数据
// 单个
const v = Vue.ref(0)
console.log(v.value) // 0
// 使用 ref 创建的响应式对象
const c = Vue.ref({a: 1})
console.log(c.value.a) // 1
// 响应式对象,外部可通过 obj.a 修改 obj的属性
const obj = Vue.reactive({
a: 1,
b: 2
})
return {
v,
c,
obj
}
},
created(){
// 修改 v 与 obj.a 的值
this.v = 3
this.c.a = 2
this.obj.a = 0
}
}
Vue.createApp(options).mount('#app')
上面主要说的是 ref 与 reactive,下面说 toRef 与 toRefs。
toRef 用于为为源响应式对象上的 property 新创建一个 ref。它接受两个参数,第一个是响应式对象,第二个是要提出来的属性名。
const options = {
setup(){
const obj = Vue.reactive({
a: 1
})
const obja = Vue.toRef(obj, 'a')
const b_obja = obj.a
return {
obj,
obja,
b_obja
}
},
created(){
console.log(this.obja)
console.log(this.obj.a)
this.obja = 2
console.log(this.obja)
console.log(this.obj.a)
this.obj.a = 3
console.log(this.obja)
console.log(this.obj.a)
}
}
Vue.createApp(options).mount('#app')
运行结果:
由此可见,虽然将 a 从 obj 中剥离了出来,但是它们之间还是保持着响应式连接,因此当改变 obja 或 obj.a 的值的时候,双方的值都会相应改变。
toRefs 则是将一个响应式对象转换为普通对象,其中结果对象的每个属性都是指向原始对象相应属性的 ref。说白了就是 toRef 的一个批量快捷操作。
const options = {
setup(props) {
const {name, age} = Vue.toRefs(props)
const obj = Vue.reactive({
a: 1,
b: 2
})
const b_obj = Vue.toRefs(obj)
return {
...b_obj, name, age, obj
}
},
created(){
console.log(this.a, this.b, this.name, this.age, this.obj)
this.obj.a = 3
console.log(this.a, this.obj.a)
this.a = 4
console.log(this.a, this.obj.a)
},
}
//在这传入 props
Vue.createApp(options, { name: '张三', age: 3 }).mount('#app')
执行结果:
可以看出 a 与 obj.a 也依旧保持着响应式连接。