Watch的官方定义
在组合式 API 中,我们可以使用watch( )函数在每次响应式状态发生变化时触发回调函数。
官方示例
const x = ref(0)
const y = ref(0)
// 单个 ref
watch(x, (newX) => {
console.log(`x is ${newX}`)
})
// getter 函数
watch(
() => x.value + y.value,
(sum) => {
console.log(`sum of x + y is: ${sum}`)
}
)
// 多个来源组成的数组
watch([x, () => y.value], ([newX, newY]) => {
console.log(`x is ${newX} and y is ${newY}`)
})
1、第一个参数一定要对象,不一定必须是响应式对象。如果传递简单数据类型,vue会在控制台提醒
2、watch函数可以传递一些其他配置,诸如deep、immediate、once,以下为每个配置项的含义(摘自官方文档)
- immediate:在侦听器创建时立即触发回调。第一次调用时旧值是
undefined。 - once:回调函数只会运行一次。侦听器将在回调函数首次运行后自动停止。
- deep:如果源是对象,强制深度遍历,以便在深层级变更时触发回调。在 3.5+ 中,此参数还可以是指示最大遍历深度的数字。
其中,immediate和once在实际使用中基本没有什么副作用,而deep在开发中可能会带来一些困扰。因此,本文主要探讨Deep这一配置项,在响应式对象、数组这两种场景下带来的影响以及一些注意项。
Deep和响应式对象
实验代码
<template>
<div>
<div>{{ `对象:${JSON.stringify(obj)}` }}</div>
<button @click="objChangePara">变更对象属性</button>
<button @click="changeObj">替换对象</button>
</div>
</template>
<script setup>
const obj = ref({ size: 12 })
const objChangePara = () => {
obj.value.size = 15
console.log('变更对象属性--------')
}
const changeObj = () => {
obj.value = { number: 20 }
console.log('整个替换对象--------')
}
console.log('初始obj', obj.value)
watchEffect(async () => {
console.log('我是WatchEffect,监听对象', obj.value)
})
watch(obj, (newval, oldval) => {
console.log('------采用watch(obj)------')
console.log('oldval:', oldval)
console.log('newval:', newval)
console.log('------------------')
})
watch(
obj,
(newval, oldval) => {
console.log('------采用watch(obj)deep------')
console.log('oldval:', oldval)
console.log('newval:', newval)
console.log('------------------')
},
{ deep: true }
)
</script>
我们先后执行变更对象单个属性、完全替换整个对象两个操作,并使用带有deep配置、不带deep配置以及普通的WatchEffect函数进行监听。实验结果为:
- 对于变更单个属性操作,仅带有deep配置的监听器监听到变化,但是新旧值保持了一致!
- 对于完全替换对象操作,三类监听器均监听到了变化,新旧值均正确输出!
Deep和响应式数组
vue3中另一个头疼就是数组的变化,数组的变更方法包括:vue封装的诸如push、pop等函数;直接更改数组内单个元素的属性;直接替换整个数组。针对这三类变更操作,我们采用:普通监听方式、deep监听方式以及对数组长度的监听三类监听方式。
<template>
<div>
<div>{{ `数组:${JSON.stringify(list)}` }}</div>
<button @click="pushItem">push</button>
<button @click="changeVal">改名</button>
<button @click="changeList">替换数组</button>
</div>
</template>
<script setup>
const list = ref([{ name: '张三' }])
const pushItem = () => {
list.value.push({ name: '王五' })
console.log('数组push了--------')
}
const changeVal = () => {
list.value[0].name = '张伟'
console.log('数组变更内部元素属性了--------')
}
const changeList = () => {
list.value = [{ name: '阿猫' }, { name: '阿狗' }, { name: '阿牛' }]
console.log('数组被整个替换--------')
}
console.log('初始list', JSON.stringify(list.value))
watch(
list,
(newval, oldval) => {
console.log('------采用watch(list)deep------')
console.log('oldval:', JSON.stringify(oldval))
console.log('newval:', JSON.stringify(newval))
console.log('------------------')
},
{ deep: true }
)
watch(list, (newval, oldval) => {
console.log('------采用watch(list)------')
console.log('oldval:', JSON.stringify(oldval))
console.log('newval:', JSON.stringify(newval))
console.log('------------------')
})
watch(
() => list.value.length,
(newval, oldval) => {
console.log('------采用watch(list.value.length)------')
console.log('oldval:', oldval)
console.log('newval:', newval)
console.log('------------------')
}
)
</script>
1. 对于使用vue官方封装的操作方法——push方法。deep监听和数组长度的监听方式均达到监听效果,而普通的监听方式无法监听到,但是deep监听的新旧值是一致的!
2. 对于直接修改单个元素的属性操作。只有deep监听方法达到监听效果!
3. 对于整个替换数组的操作。三种监听方式均达到了监听效果,同时新旧值是不一致的。
结论
综合考虑,使用watch函数监听普通对象时,如果不涉及对下层属性的监听,可以关闭deep配置。如果只想监听到浅层属性的变更,可以为deep属性指示最大遍历深度的数字,降低计算消耗。
而对于数组的监听,建议顺手加上deep配置,否则连最基本的push操作都无法监听到!!!
当然如果实际场景中,每次操作都会引起数组长度发生变化,那么监听数组长度也是可以的。