持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情
上面我们看了vue3的数据定义的区别,下面就来看看watch与computed在vue3中有何不同吧!
watch
vue3的watch和vue2的watch差别可就大了啊,我在项目中有一个需求,就是每一个职位都有默认的权限,我们在创建新的用户时,我们需要给其设置职位,且可以给该用户设置其职位默认权限之外的权限。那么我在选择职位的时候(下拉框展开),其对应的权限也应该更新,这时候就需要使用watch了,但是这个Vue3的watch可就区别真的很大...
vue3的watch是一个函数,它有3个参数:
- 第一个参数:要监视的数据
- 第二个参数:一个匿名函数(可以得到oldValue和newValue),变化后对其进行的反应;
- 第三个参数:配置项,一个对象,里面可以设置是否开启深度监听以及是否立即执行;
watch监视ref包装的原始数据
watch监视ref包装的原始数据基本还是正常的。
import {ref,reactive,watch} from 'vue'
...
let num = ref(0);
watch(num,(newValue,oldValue)=>{
console.log('num变化了',newValue,oldValue)
},{immediate:true})
...
但是要注意的是:watch监视ref包装的原始数据类型,不能.value
也就是说:上面的代码中,不能写watch(num.value,(newValue,oldValue)=>{...})。因为再.value的话,监视的就是0,就是一个值了,一个值咋可能会变化呢??其实监视的watch,监视的就这个这个变量的内存内容有没有改变,我是这样理解的:ref返回的是一个对象,而watch监视发现这个对象地址内存指向的堆空间里面有个属性:value,当value的内容变了,它就监视到了。
watch监视reactive包装的引用类型数据
这个就坑了,首先,它对于reactive包装的引用类型数据,它是强制开启了deep:true深度监视的,且你设置为false关闭了也没用。
第二点:他的oldValue 和newValue是一样的了,都是最新的了,也就是说,他得不到oldValue属性值了。
第三点,如果需要监视对象的某个属性,不能直接写这个属性,需要写一个回调函数的形式,返回这个要监视的属性(在这里可以得到oldValue)。
<template>
<h1>我的名字:{{person.name}}</h1>
<h2>我儿子的名字:{{person.son.name}}</h2>
<button @click="changeMyself">点我改变我自己</button>
<button @click="changeMySon">点我改变我儿子</button>
</template>
<script>
...
let person = reactive({
name:'ly',
son:{
name:'yl',
son:{
age:1
}
}
})
// 监视person整个对象
watch(person,(newValue,oldValue) => {
console.log('person.son.name',newValue === oldValue);
},{deep:false})
// 监视perosn的name
watch(()=>person.name,(newValue,oldValue)=>{
console.log('person.name',newValue,oldValue)
})
const changeMyself = () => {
person.name = 'bbb'
};
const changeMySon = () => {
person.son.name = 'aaa';
};
...
</script>
我们发现,当我们设置deep为false后,vue3还是能监视到person.son.name的变化,同时newValue === oldValue返回的是true。
watch可以监视多个属性
此外,我觉得watch有一个好的地方就是在于它现在可以一次监视多个属性的变化了(可以一次返回多个监视属性的新值和旧值,感觉比较方便)。但是返回的newValue和oldValue也变成了数组。
...
let person = reactive({
name: "ly",
sex:'男',
age:18,
});
watch(()=>[person.sex,person.age],(newValue,oldValue)=>{
console.log('性别或者年龄变化了',newValue,oldValue);
})
...
watch监视的数据是对象且不是reactive包装的对象
这个意思就是如果我们监视的是一个reactive包装的对象一个属性,但是这个属性又是一个引用类型。那么这时候,watch就没有开启深度监视了,也就我们得自己手动去开启。
也就是说watch开启深度监视只针对reactive包装的那个对象
watchEffect
它和watch的区别就是:
- 不需要手动传入依赖
- 每次初始化时会执行一次回调函数来自动获取依赖
- 无法获取到原值,只能得到变化后的值
<script>
import {reactive, watchEffect} from 'vue'
export default {
setup() {
const state = reactive({ count: 0, name: 'zs' })
watchEffect(() => {
console.log(state.count)
console.log(state.name)
/* 初始化时打印:
0
zs
1秒后打印:
1
ls
*/
})
setTimeout(() => {
state.count ++
state.name = 'ls'
}, 1000)
}
}
</script>
也就是说,它获取不了oldValue,只有newValue,且和computed一样,当回调函数中依赖的数据发生改变时,回调函数就会触发。此外该回调函数在初始化时也会触发一次。
watchPostEffect
Vue的DOM更新我们都知道是异步的,底层是用的Promise.then(和nextTick同理),所以当我们的数据源发生改变时,DOM还不会发生改变,也就是说,watch和watchEffect是只能得到DOM更新之前的DOM,而不会得到更新后的DOM,如果我们需要得到更新后的DOM,那么就需要使用nextTick。但是Vue3新增了一个选项:flush。
watch(source, callback, {
flush: 'post'
})
这样这个回调函数就会在DOM渲染更新完毕后再执行,这样就可以拿到新的DOM了。
此外,还有一个新的方法:watchPostEffect()它的用法和watchEffect一模一样,只是执行时机不同罢了。
更新:但是现在我发现了有个问题,watch里面拿到的DOM是更新后的DOM了,亲测真实,我想是不是官方更新了,将watch也变成了一个异步的任务,当数据源变化时,会先调用DOM更新,再调用watch,也就是说watch现在默认只能得到更新后的DOM了(因为我将flush设为pre也没行,拿到的也是更新后的DOM)。
<template>
<p ref="firs">11:{{ firstName }}</p>
<p>11:{{ lastName }}</p>
<button @click="changeFirs">点我</button>
</template>
<script lang="ts">
import { reactive, toRef, ref, toRefs, computed, watch } from "vue";
import Child from "./components/Child.vue";
export default {
setup() {
const firstName = ref("lei");
const lastName = ref("yan");
const firs = ref(null);
watch(firstName, (newV, oldV) => {
console.log(firs.value);
console.log(newV,oldV);
},{ deep: true });
function changeFirs() {
firstName.value = 'leiii'
}
return { firstName, lastName, firs,changeFirs };
},
components: {
Child
}
};
</script>
点击之后:
computed
vue3的computed感觉和vue2的computed区别不大,它也是有两种方式,一种是简写版,传入一个getter函;另一版本是完整版,可以同时进行getter和setter。但是官方建议的是:避免直接修改计算属性值,也就是说应该尽量只使用computed来计算获取值。
下面来看看vue3的computed的用法吧:
简写:
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed(()=>{
return firstName.value + ' ' + lastName.value
})
完整版:
const fullName = computed({
// getter
get() {
return firstName.value + ' ' + lastName.value
},
// setter
set(newValue) {
// 注意:我们这里使用的是解构赋值语法
[firstName.value, lastName.value] = newValue.split(' ')
}
其他的和Vue2的区别是一样的,比如拥有缓存,当所依赖的数据没有发生变化时,再次调用fullName并不会触发getter。此外还有就是拥有“懒加载”的含义,如果我们使用计算属性声明了fullName,但是我们并没有使用fullName的时候,fullName并不会触发它的getter。而watch就会默认执行一次,因为要获取oldValue然后存储oldValue。
此外,computed不应该有其他的作用,比如进行DOM的操作或者进行异步的返回等(因为异步的返回并没有作用),而watch就可以进行异步请求。
const fullName = computed(() => {
let ful='';
setTimeout(()=>{
ful = firstName.value + " " + lastName.value
},1000)
return ful;
});
这样返回的就是空字符串。