在写篇文章之前用过只用过vue2中的watch监听,最近在学vue3,然后对这两个版本的watch监听使用的不同之处做个总结,记录下vue3中watch中的具体使用方法和注意事项
watch在Vue2和Vue3中有哪些不同
-
语法不同
- Vue 2 中,watch 选项接收一个对象,键是要观察的属性名,值是回调函数或者包含选项的对象。
- Vue 3 中,watch 可以通过 watch 函数来实现,支持多种参数传递方式,更加灵活。
-
监听对象属性不同
- Vue 2 中,直接监听对象的某个属性变化时,如果对象的属性被添加或删除,不会触发监听。
- Vue 3 中,使用 watch 函数并结合 deep 选项可以更方便地监听对象属性的深层次变化,包括属性的添加和删除。
-
监听多个数据源
- Vue 2 中,要监听多个数据源需要分别配置多个 watch 选项。
- Vue 3 中,可以在一个 watch 函数中同时监听多个数据源。
-
清除副作用
- Vue 3 中停止监听的函数 watch,方便在组件卸载等场景中清除副作用
vue3 watch的具体应用
可监听的类型
在vue3中 文档中有说明 watch可监听的类型有四种:
- 一个 ref(ref定义的数据)
- 一个响应式对象(reactive定义的数据)
- getter函数(函数返回一个值)
- 一个包含上述内容的数组
watch函数参数
watch函数一共有三个参数
-
第一个参数是监听的的源,源可为上方的四种类型
-
第二个参数是回调函数,返回新值和旧值和vue2一样,还有第三个参数onCleanup函数。onCleanup可以用来注册清理回调,在下次侦听器执行前会被调用。
-
第三个参数 是配置项(非必填可选择)包含一下配置
- immediate: 值为true,会在侦听器创建时立即执行回调。
- deep: 值为true 会深度监听对象内部的变化。
- flush: 指定回调函数的执行时机
- post (默认值): 侦听器回调会在 DOM 更新之后执行。
- pre: 与post相反,表示侦听器回调会在 DOM更新之前执行 的更新。这个选项适用于需要在 DOM 更新之前访问旧 DOM 的场景。
- sync: 表示侦听器回调会在数据变化时立即同步执行。这通常会导致更高的性能开销,因为它会阻止其他任务的执行,直到侦听器回调完成。这个选项适用于需要立即响应数据变化,并且变化不频繁的场景。
- onCleanup: 一个在侦听器停止侦听之前执行的函数(可以用来清除无效的副作用,例如等待中的异步请求。)
- onTrack: 在依赖项被追踪时触发
- onTrigger: 在依赖项的值发生变化并触发更新时触发
监听ref类型数据
监听ref的普通类型
<template>
<div>
<h1>姓名:{{ man }}</h1>
<button @click="changeName">改变姓名</button>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
let man = ref('帅哥')
function changeName(){
man.value += '~真帅'
}
// 这里直接写man就可以,不要写man.vue
watch(man,(newValue,oldValue) => {
console.log('改变了', newValue,oldValue)
})
</script>
停止watch的监视
watch函数返回一个用于停止监听的函数,执行这个返回函数就会停止watch的监视
<template>
<div>
<h1>计数:{{ num }}</h1>
<button @click="changeName">累加</button>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
let num = ref(0)
function changeName(){
num.value += 1
}
let stopWatch = watch(num,(newValue,oldValue) => {
console.log('改变了', newValue,oldValue)
// 当num 大于10的时候 停止监视
if(newValue > 10){
stopWatch()
}
})
</script>
监听ref的对象类型
定义ref的对象类型,watch监听默认情况下只监听这个对象的本身(存储地址),而对象内部的元素值变化不会触发监听
<template>
<div>
<h1>姓名:{{ man.name }}</h1>
<h1>年龄:{{ man.age }}</h1>
<button @click="changeName">改变姓名</button>
<button @click="changeAge">改变年龄</button>
<button @click="changeMan">改变人</button>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
let man = ref({
name: '帅哥',
age: 20,
})
function changeName() {
man.value.name += '~帅'
}
function changeAge() {
man.value.age += 1
}
function changeMan() {
man.value = {
name: '美女',
age: 18,
}
}
watch(man, (newValue, oldValue) => {
console.log('改变了', newValue, oldValue)
})
</script>
<style>
button {
margin: 10px;
}
</style>
如果也想要监听内部的元素值变化,可开启deep,但要注意的是,这时改变对象内部元素的时候 newValue, oldValue 是一样的都是新值,只有改变整个对象,才能拿到oldValue旧值
watch(man, (newValue, oldValue) => {
console.log('改变了', newValue, oldValue)
}, { deep: true})
| 未开启deep | 开启deep |
|---|---|
监听一个函数
如果只想监听对象中一个元素,例如只想监听name的变化,可以改为监听一个函数,函数返回这个对象的元素,但需要注意的是 这种写法改变整改对象(实例中的改变人方法)也会触发监听!
错误的写法,不能直接监听value,因为 它不属于watch可监听的四种类型,所以不支持
watch(man.value, (newValue, oldValue) => {
console.log('改变了', newValue, oldValue)
}, { deep: true })
// 默认只监听 man这个对象的地址,对象内部的元素值变化不会触发监听
watch(() => man.value.name, (newValue, oldValue) => {
console.log('改变了', newValue, oldValue)
}, { deep: true })
监听reactive
注意watch直接监听一个reactive是默认开启深度监听的,也就修改对象中任何一个值都会触发监听,且这种深度监听是不能关闭的。即便设置deep为false 也是无效的
当想修改整个reactive对象时,要注意,像以下这几种写法 都是错误的,这样会让man对象失去响应式
- man={***}
- man=reactive({***})
- man.value={***}
- man.value=reactive({***})
监听 reactive例子
<template>
<div>
<h1>姓名:{{ man.name }}</h1>
<h1>年龄:{{ man.age }}</h1>
<h1>我得第1部手机:{{ man.phones.phone1 }}</h1>
<h1>我得第2部手机:{{ man.phones.phone2 }}</h1>
<button @click="changeName">改变姓名</button>
<button @click="changeAge">改变年龄</button>
<button @click="changePhone1">改变第一部手机</button>
<button @click="changePhone2">改变第二部手机</button>
<button @click="changePhone">改变所有手机</button>
<button @click="changeMan">改变这个人</button>
</div>
</template>
<script setup>
import { reactive, watch } from 'vue'
let man = reactive({
name: '帅哥',
age: 20,
phones: {
phone1: '红米',
phone2: '苹果',
}
})
function changeName() {
man.name += '~帅'
}
function changeAge() {
man.age += 1
}
function changePhone1() {
man.phones.phone1 = '华为'
}
function changePhone2() {
man.phones.phone2 = '小米'
}
function changePhone() {
man.phones = {
phone1: 'vivo',
phone2: 'oppo',
}
}
function changeMan() {
// // 错误的写法 1 数据不生效,页面不相应,watch也不会触发
// man = reactive({
// name: '大神',
// age: 30,
// phones: {
// phone1: '诺基亚1',
// phone2: '诺基亚2',
// }
// })
// // 错误的写法 2 数据不生效,页面不相应,watch也不会触发
// man = {
// name: '大神',
// age: 30,
// phones: {
// phone1: '诺基亚1',
// phone2: '诺基亚2',
// }
// }
// // 错误的写法3 数据不生效,页面不相应,watch会触发,
// man.value = {
// name: '大神',
// age: 30,
// phones: {
// phone1: '诺基亚1',
// phone2: '诺基亚2',
// }
// }
// 正确的写法 watch响应 回调里的新旧值一样
Object.assign(man, {
name: '大神',
age: 30,
phones: {
phone1: '诺基亚1',
phone2: '诺基亚2',
}
})
}
watch(man, (newValue, oldValue) => {
console.log('改变了', newValue, oldValue)
})
</script>
<style>
button {
margin: 10px;
}
</style>
</script>
第一种情况
如果想要只监听man对象中的某一个元素,方法的话和上文中监听ref的方法一样, 把监听源换成一个函数,函数返回这个reactive对象的属性
错误的写法
watch(man.age, (newValue, oldValue) => {
console.log('改变了', newValue, oldValue)
})
正确的写法
watch(() => man.age, (newValue, oldValue) => {
console.log('改变了', newValue, oldValue)
})
第二种情况
如果想要只监听man对象中的多个元素,例如只想监听man的age和 phones 下的phone1
监视源就要改为一个数组,数据的元素为函数, 要注意的是此时的回调函数里返回的 newValue、oldVaule 也就只剩下监视的这两个值了
watch([() => man.age, () => man.phones.phone1], (newValue, oldValue) => {
console.log('改变了', newValue, oldValue)
})
第三种 只监听 age和 phones,这时候 phones 就不用函数了
watch([() => man.age, man.phones], (newValue, oldValue) => {
console.log('改变了', newValue, oldValue)
})
总的来说如果要监听对象里的某个属性,那监视源就要写函数式