computed 计算属性
在vue3中,虽然也能写vue2的computed,但还是更推荐使用vue3语法的computed。
在Vue3中,计算属性是组合式API,要想使用computed,需要先对computed进行引入:
import { computed } from 'vue'
computed是一个函数。它的参数接收一个函数,这个函数可以是普通函数,也可以是箭头函数,由于vue3中没有this,所以无需关注函数类型对this指向的影响。这个函数就是计算的方法函数。
<template>{{ totName }}
</template><script>import { computed } from 'vue'export default{setup(){let name = 'dog'let age = 12let totName = computed( ()=>{return name + age;})return{totName,}}}
</script>
但是通过这种方式定义的计算属性,是只读的,无法对属性进行修改。如果计算属性同时需要读取和修改,要通过下面这种形式:
传入computed的参数是一个对象,在对象中配置get和set。
<template>{{ totName }}<button @click=change>click</button>
</template><script>import { ref,computed } from 'vue'export default{setup(){let name = ref('dog')let age = ref(12)let totName = computed( {get(){return name.value + age.value;},set(value){name.value = value[0];age.value = value[1];}})function change(){totName.value = ['cat',10];}return{totName,change}}}
</script>
watch
在vue3中,watch也是组合式API,也需要引入:
import { watch } from 'vue'
与其他组合式API一样,其实组合式API就是Vue3提供的一些函数,因此watch也是一个函数。
watch的第一个参数是需要监视的数据,第二个参数类似handler函数,当要监视的数据改变时,调用这个函数,函数提供两个参数 监视数据新的值,监视数据旧的值。第三个参数是配置对象,可以用于配置immediate和deep。
在vue2中,watch配置项只有一个,但是在vue3中,可以使用多次watch函数。
如果想用vue3监控多个数据的改变,可以写多个watch函数,也可以把第一个参数写成数组,数组中的每个元素都是想要监视的数据。第二个参数中,newValue和oldValue也是数组的形式,数组中第n个元素对应第n个被监视的数据的newValue和oldValue。
要注意:
如果watch监视的数据是一个对象,当修改了对象中某个属性的数据,在watch的回调函数中,newValue和oldValue值是一样的。也就是说,通过ref定义的普通类型响应式数据,用watch进行监测,new和old的值才是正确的。如果通过ref或reactive创建的响应式对象,在watch中无法获取正确的new和old值。如果是ref定义的对象,需要在监测对象里写.value语法,且和reactive一样,newValue和oldValue都是newValue。这个问题目前无法解决,但是,由于在开发中,需要oldValue的情况并不多,如果确实需要oldValue,要把监测的数据定义成普通类型的数据。
除此之外,如果监测的数据是用reactive创建的响应式,即使对象的层级较多,不设置deep:true,也能对对象进行深度监测。如果不需要深度监测,需要设置deep:false。
如果只想监测对象中某个具体的属性,而不是整个对象中所有属性,在第一个参数上写对象.属性,用这种语法,监测是无法生效的。watch只能监测ref、reactive。如果想要监测对象上某个具体的属性,第一个参数应该是一个函数,函数的返回值是对象.属性,watch才能够正常监测。而且,这时候newValue和oldValue是正确的数值。
如果想监视对象中的某些属性,需要写一个数组,数组中每个元素都是函数,函数的返回值是需要监视的数据,且数据是普通数据类型。通过这种语法,可以正确获取newValue和oldValue。
但如果要监测的是对象中的某个属性,这个属性还是一个对象,此时需要开启deep:true,否则无法监测到数据的变更。
也就是说,对于watch,根据监测对象的不同,会产生以下现象:
1.如果监测整个对象,默认强制开启deep:true。
2.如果监测函数中的某个属性,需要以函数返回值的形式返回这个属性。
3.如果想要正确监测对象中的对象属性,需要打开deep:true设置。
watchEffect
watchEffect是一个函数,使用之前需要先引入:
import { watchEffect } from 'vue'
watchEffect的参数是一个回调函数,它会监测内部的回调函数涉及了哪些变量,当涉及的变量数值发生变化时,回调函数就会被触发。watchEffect初始就会被执行一次,然后当内部涉及的数据变化时也会执行。
watchEffect和watch和computed:
watchEffect和watch都是用于监测数据,在数据发生变化时调用回调函数,但是不同的是,watch既要知名监视的属性,也要指明回调。watchEffect不需要指明监视的属性,只需要指明回调函数,在回调函数中用到哪个属性,就会监视涉及的属性。
watchEffect和computed:watchEffect回调函数的触发逻辑和computed类似,都是初始时会执行一次,然后当内部依赖的数据发生变化时,执行一次。但computed是计算属性,需要返回计算值。watchEffect是监视属性,它关注过程,无需返回值。
Vue3生命周期
在vue3中,销毁组件时,触发的是beforeUnmount和unmounted钩子,而没有beforeDestroy和destroyed钩子。在vue3中,称为卸载。
对于vue2来说,如果调用new Vue时,没有传递el,那么会走到created钩子之后,才会判断el是否配置了。但在vue3中,在一开始,创建了app实例之后,需要进行app.mount,然后才进入生命周期。
在vue3中,当组件卸载时,会触发beforeUnmount钩子和unmount钩子。vue3把生命周期分为挂载->卸载流程。除此之外,vue3生命周期的各种概念和逻辑都跟vue2一样。
但是,vue3中,如果想通过setup组合式API去使用生命周期钩子,钩子的名称会发生变化:
beforeCreate -> setup()
created -> setup()
在vue3中,并没有给beforeCreate和created钩子提供API,但可以把setup看做beforeCreate和created。
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeUnmount -> onBeforeUnmount
unmounted -> onUnmounted
<script>import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'export default{setup(){onBeforeMount(()=>{console.log('onbeforeMount')})onMounted(...)onBeforeUpdate(...)onUpdated(...)onBeforeUnmount(...)onUnmounted(...)}}
</script>
要使用setup组合式API,都需要先进行引入,他们都是函数。
这些函数都接收一个函数作为参数,在对应的时机,这个参数函数就会被调用。
在vue3中,由于可以同时定义生命周期配置项,以及setup中的组合式API生命钩子,有时候会产生令人难以理解的执行顺序,如果同时定义了配置项和组合式,实际上,在挂载时,setup()函数是最先被执行的->beforeCreate->created->onBeforeMount->beforeMount->onMounted->mounted。更新时的执行顺序是onBeforeUpdate->beforeUpdate->onUpdated->updated。卸载时的执行顺序是onBeforeUpdate->beforeUpdate->onupdated->updated。也就是说,组合式API生命周期的执行时机比配置项快一些。
但是一般不推荐同时使用生命周期配置项和setup组合式API生命钩子。
hooks
hook也是一个函数,它把setup函数中使用的组合式API进行了封装。
原始功能:
<template><h2>{{ num }}</h2><button @click=change>click</button>
</template><script>import { onMounted, ref } from 'vue'export default{setup(){let num = ref(0);function change(){num.value++;}onMounted(()=>{num.value = 5;})return{num,change,}}}
</script>
对于某个功能,可以把这个功能涉及的数据、方法、生命周期逻辑抽出来,抽成一个函数,把这个函数单独放在js中,函数返回功能需要的数据或方法,并对外暴露。当vue文件引入这个js时,就可以复用这个功能(因为vue文件获取了这个函数,函数里包含了使用功能需要的所有代码逻辑)。
一般这个js文件就是一个hook的思想,它一般放在src下的hooks文件里。
hook类似于vue2的mixin。通过自定义hook,可以复用代码,让setup中的逻辑更清晰。
组合式API
除了前面学到的组合式API,vue3中还具有很多其他的API,这里先学习其中一部分剩余API。
toRef
toRef用于创建一个ref对象,它的value值指向另一个对象中的某个属性。
有时候,希望拿一个变量存储对象中的属性,而且希望对象中的属性改变时,变量中存储的属性也发生改变,当变量中数据改变时,有时候也希望反馈到对象上。
直接通过变量存储对象属性时,无法达成这种效果。
但toRef可以把一个并非ref响应式的数据变成ref。toRef接收两个参数,第一个参数是目标对象,第二个参数是想创建ref的对象上的属性名。如果想ref的属性是嵌套的,可以通过第一个参数设置。
toRefs
可以用于批量创建引用。
toRef一次只能处理一个属性,toRefs一次可以处理多个属性。
toRefs(对象)
toRefs可以用于对整个对象创建响应式副本。
<template><h2>{{ cat }}</h2><h2>{{ name }}</h2><h2>{{ hobby }}</h2>
</template><script>import { reactive,toRef,toRefs } from 'vue';export default{setup(){let data = reactive({name:'cat',age:10,hobby:{sleep:'day',walk:'night',}})let name = toRef(data,'name');let hobby = toRef(data.hobby,'sleep');let cat = toRefs(data); return {name,hobby,cat}}}
</script>
shallowReactive
浅Reactive。
只实现对象中的第一层数据的响应式,如果第二层数据发生变化,不触发响应式。
shallowRef
浅Ref。
不处理对象响应式,如果传入的是对象,无法实现响应式。
shallowReactive与shallowRef:
shallowReactive只处理对象最外层属性的响应式。shallowRef只处理基本数据类型的响应式,不对对象进行响应式。
使用场景:
如果对象的层级结构很深,但数据变化时只涉及最外层的变化,就可以使用shallowReactive创建响应式。
如果一个对象,后续不会修改对象中的数据,而是生成新对象来替换,可以使用shallowRef(shallowRef可以监测到对象整体的替换修改,但是无法监测到内部属性的变化)。
readonly
让一个响应式数据变为深度只读。
let data = reactive({name:'cat',age:10,hobby:{sleep:'day',walk:'night',}})let cat = readonly(data)
shallowReadonly
让一个响应式数据变成浅只读。
第一层数据只可读,第二层及之后的数据可读可改。
readonly和shallowReadonly应用场景:当数据完全或者一定程度上不希望被修改时。
虽然在一开始定义数据时,数据创建的是响应式,初衷似乎是希望页面对数据的修改发生响应,而后又设置只读,不希望数据被修改,这种行为看起来有些令人迷惑,但在实际使用中,可能数据是其他人创建的,而目前我们不希望这个数据可写。就可以使用readonly和shallowReadonly对数据进行限制。