场景:在vue中,使用watch 的场景是很常见的。编写业务代码时,需要监听一个或多个值的变化时,经常性会使用watch,日常使用就不提了,直入主题,来一段使用watch的简单代码,有一定前端水平的人应该能立马发现问题。
import { watch, onMounted, ref } from 'vue'const people = ref({ name: '小明', age: 18 })watch(() => [people.value.name],(val, old) => {console.log(val, old, '监听了数组()=>[], val,old')},{ immediate: true }
)watch(() => people.value.name,(val, old) => {console.log(val, old, '监听了基本类型number()=>, val,old')},{ immediate: true }
)onMounted(() => {let age = people.value.age// 每2秒年龄+1setInterval(() => {age += 1people.value = {...people.value,age}}, 2000)
})
创建了一个人叫小明的people变量,每过两秒,小明的年龄都加1,这时候,两段监听函数的区别是,前面是监听了小明名字是数组类型,后面的监听是监听具体小明名字,会打印哪一段?还是两段都打印?还是都不打印?
注意:这里onMounted里函数的计时器改变的是“年龄”(age),而监听的是“名字”(name)!
打印结果:打印了监听()=>[]
其实原因在onMounted函数里改变值的写法,是把整个对象给替换掉了
// 是这样赋值的
people.value = xxx;// 并没有这样赋值
people.value.age = xxx;
这就导致存储people.value的值重新被分配了,内存地址发生了改变,但是存储 name 的值却还是原来的,name 引用的内存地址没有改变。回想一下vue3 的监听原理,用的是proxy 监听对象,reflect 反射对象。监听数组时,对数组里的每个object都都进行了一次proxy 监听,当object改变了,则通知订阅者,而监听的的是基本类型时,会监听基本类型的数值有没有被改变。如果这里watch 监听的是的是"age",那不管是数组还是number,都会执行副作用函数。
会导致什么问题?
举个例子,你有这么一个场景,你的路由地址有n个参数,其中有2个是你需要监听的参数,当监听到者两个参数发生变化时,你可能会这样写:
import { watch, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()// 其实我只想监听today和where的变化,才想执行副作用,比如周二游泳,周三跑步
// other改变时并不想执行该副作用
watch(() => [route.query.today, route.query.where],([today, where]: any[]) => {console.log(`today:${today},where:${where}`)doSomething(today, where)},{immediate: true}
)function doSomething(today: string | undefined, where: string | undefined) {// 当只有星期和地址改变的时候,我想执行这个函数console.log(`今天是星期${today},我想去${where}`)
}onMounted(() => {console.log('onMounted')let other = '0'setInterval(() => {other = Number(other) + 1 + ''console.log('other:', other)router.push({path: '/',query: {other,today: '一',where: '公司摸鱼'}})}, 3000)
})
实际情况却是:today和where 没有改变,other变了,天晴了,雨停了,doSomething执行了,我又在摸鱼了
看了上面监听执行的逻辑性问题,如果你的基础水平不够扎实,你应该避免这种监听一个数组,数组中包含多个对象的值的写法,不要太依赖于watch,watch当然是好用的,只是我们在编写代码的时候,总不可能考虑得太全的。
解决方案:
可以分开多个watch 来监听:
watch(()=>route.query.today,()=>{ doSomething() });
watch(()=>route.query.where,()=>{ doSomething() });
如果觉得重复代码太多,就是想写数组形式的监听可以吗,可以的;
加一个if判断,if(newValue.today !== oldValue.today){ doSomething() }
所以,当你想初始化一个变量的时候,要考虑到这个变量有没有监听代码,赋值为 null 时会不会产生相应的问题,它是官方推荐的方法,但可不知道你这是不是官方推荐的写法~
所以watch监听时,不要太依赖与watch 的副作用执行,请多加一层判断