5.1 watch概述
作用:监视数据的变化(和Vue2中的watch作用一致)
特点:Vue3中的watch只能监视以下四种数据:
ref
定义的数据。reactive
定义的数据。- 函数返回一个值(
getter
函数)。- 一个包含上述内容的数组。
5.2 监听ref基本类型
1、Vue2
对于基本类型的监听,Vue2就是一个函数,里面有newValue和oldValue返回新值和旧值。
watch: {
sum(newValue, oldValue) {
......
}
}
2、Vue3
在Vue3里使用watch要先import引入,而且watch里面传入两个参数:第一个是监视的属性名,第二个是回调函数。
import {watch} from 'vue'
watch(sum, (newValue, oldValue) => {
......
})
这里举一个最简单的例子:点按钮,数字+1。数字属于基本类型,我们对它进行监听:
<template>
<div class="person">
<h2>当前求和为:{{sum}}</h2>
<button @click="changeSum">点我sum+1</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {ref,watch} from 'vue'
// 数据
let sum = ref(0)
// 方法
function changeSum(){
sum.value += 1
}
watch(sum,(newValue,oldValue)=>{
console.log('sum变化了', newValue, oldValue)
})
</script>
注意:watch的第一个参数传的是变量名,而不是它的value值,千万不要写成变量名.value。虽然它是ref类型,真正的值是.value,但watch并非监听它的value值。
5.3 监听ref对象类型
先回顾一下Vue2监听对象的语法,它是一个对象,里面通过handler方法来获取新值和旧值。
watch: {
sum: {
handler(newValue, oldValue) {
......
}
}
}
Vue3如果监听的是一个对象类型,这个比较有意思,它会出现两种情况:第一个监听的是整个对象的地址是否改变;第二个是监听对象里的属性是否发生改变。
5.3.1 监听对象地址
监听整个对象地址是否发生改变,这个是最简单的,直接把对象放进去watch监听就可以了。
在下面的例子里有3个按钮,”修改名字”和”修改年龄”都是改变对象里的属性值,而”修改整个人”是改变整个对象。
<template>
<div class="person">
<h2>姓名:{{ person.name }}</h2>
<h2>年龄:{{ person.age }}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="changePerson">修改整个人</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {ref,watch} from 'vue'
// 数据
let person = ref({
name:'张三',
age:18
})
// 方法
function changeName(){
person.value.name += '~'
}
function changeAge(){
person.value.age += 1
}
function changePerson(){
person.value = {name:'李四',age:90}
}
watch(person,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue)
})
</script>
我们发现,直接把对象放进去watch进行监听,它是无法监听内部属性的变化的,它只能监听整个对象有没有被重新赋值,也就是它的地址有没有改变。
5.3.2 监听对象属性
在实际的开发中,我们更多的是监听对象里的属性变化。此时需要手动开启深度监听,也就是设置deep: true。
我们在这里继续做Vue2与Vue3的语法对比:
1、Vue2
在Vue2中,需要深度监听,只需要在与handler平级的地方设置deep:true即可。
watch: {
person: {
handler(newValue, oldValue) {
......
},
deep: true
}
}
2、Vue3
在Vue3中,它同样是设置deep:true,区别就在于它是在watch的第三个参数里设置,第三个参数是一个对象,用来设置watch的一些配置项,比如{deep: true}。
watch(person,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue)
}, {
deep: true
})
我们发现,设置了deep:true深度监听之后,不管是属性值发生变化,还是整个对象发生变化,它一样能监听得到。
当然除了deep:true之外,还有immediate:true也是比较常用,这个是立即监听的意思,它是先执行监听的函数,因此在初始化数据的时候,它就会立即调用一次watch监听。它的作用跟Vue2是完全一致的,在这里就不再举例详述。
注意:我们在监听对象的时候,如果是整个对象发生改变,那么它是可以识别新对象与旧对象的。但如果对象不变,只是内部的属性发生改变,那么它的newValue与oldValue是一致的。
5.3.3 示例代码
请打开F12控制台观看打印输出信息。
5.4 监听reactive对象类型
5.4.1 reactive与ref的监听对比
reactive只能定义对象类型,它不像ref那样可以定义基本和对象两种类型。reactive与ref在监听的时候,主要有以下两点区别:
1、reactive类型是不允许改变整个对象的,也就是说不能给它赋值一个新对象,因此它就不会出现监听整个对象发生变化的情况
2、默认开启深度监听
5.4.2 监听reactive
其实监听reactive跟ref是一样的,也是把对象传进去watch里进行监听,不过它比ref更加方便,直接就可以监听到里面的属性变化,不需要设置deep:true。准确来说是Vue3强行设置深度监听,哪怕你设置deep:false也是没用的。
<template>
<div class="person">
<h2>姓名:{{ person.name }}</h2>
<h2>年龄:{{ person.age }}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="changePerson">修改整个人</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {reactive,watch} from 'vue'
// 数据
let person = reactive({
name:'张三',
age:18
})
// 方法
function changeName(){
person.name += '~'
}
function changeAge(){
person.age += 1
}
function changePerson(){
Object.assign(person,{name:'李四',age:80})
}
watch(person,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue)
})
</script>
注意:示例中修改整个人(整个对象),它实际上是使用了Object.assign()方法,这个方法实际上并没有改变对象地址,它只不过是批量改变对象的各个属性而已。
5.4.3 示例代码
请打开F12控制台观看打印输出信息。
5.5 监听对象属性
除了监听整个对象以外,还可以指定监听对象的某一个属性。而对象里的属性,又可以分成两种类型:一个是基本类型的属性,一个是对象类型的属性。
上述例子中,前面两个按钮是修改对象里的基本类型,后面3个按钮是修改对象里的对象类型,我们对此逐一分析:
5.5.1 基本类型属性
1、Vue2
在Vue2中,监听对象里的基本类型属性,以对象.属性用一个函数返回最新值和旧值。
watch: {
'person.name'(newValue, oldValue) {
......
}
}
2、Vue3
在Vue3监听一个对象,是直接把对象放进去watch里监听即可。现在要监听对象里的属性,我们的第一反应就会把对象.属性放进去监听。
例如在例子中想监听对象的名字,很容易就会写成watch(person.name, ()=>{}),如下所示:
<template>
<div class="person">
<h2>姓名:{{ person.name }}</h2>
<h2>年龄:{{ person.age }}</h2>
<h2>汽车:{{ person.car.c1 }}、{{ person.car.c2 }}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="changeC1">修改第一台车</button>
<button @click="changeC2">修改第二台车</button>
<button @click="changeCar">修改整个车</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {reactive,watch} from 'vue'
// 数据
let person = reactive({
name:'张三',
age:18,
car:{
c1:'奔驰',
c2:'宝马'
}
})
// 方法
function changeName(){
person.name += '~'
}
......
watch(person.name, (newValue,oldValue) => {
console.log('person.name变化了',newValue,oldValue)
})
</script>
我们发现,我们直接以对象.属性的方式放进去监听,Vue3是不允许的,它会发出警告:这是一个无效的监听源,它只允许以下四种类型:getter函数、ref类型、reactive类型、以上3种类型组合的数组。
显然这个person.name只不过是一个字符串,属于基本类型属性,并非以上四种类型之一。
那么getter函数又是什么呢,其实这个警告说得比较含糊,反而官方文档说得非常接地气:所谓getter函数,它就是返回一个值的函数。
文档地址:cn.vuejs.org/api/reactiv…
既然了解了getter函数,那么我们只需要使用箭头函数将对象属性包裹一下就可以了:
watch(() => person.name, (newValue,oldValue) => {
console.log('person.name变化了',newValue,oldValue)
})
此时它就只监听对象的name属性,其它属性都不会监听:
5.5.2 对象类型属性
1、Vue2
Vue2监听对象里的对象类型属性,其实跟监听普通对象是一样的,以对象.属性作为键,通过handler方法来获取新值和旧值。
watch: {
'person.car': {
handler(newValue, oldValue) {
......
}
}
}
2、Vue3
Vue3对于对象里的对象类型属性的监听,它有两种方式:一是直接监听,二是使用箭头函数包裹后再监听。
(1)直接监听属性对象
基本类型需要箭头函数包裹成getter函数才能监听,而对象类型不需要,它直接传入watch即可监听。
let person = reactive({
name:'张三',
age:18,
car:{
c1:'奔驰',
c2:'宝马'
}
})
function changeC1(){
person.car.c1 = '奥迪'
}
function changeC2(){
person.car.c2 = '大众'
}
function changeCar(){
person.car = {c1:'雅迪',c2:'爱玛'}
}
watch(person.car,(newValue,oldValue)=>{
console.log('person.car变化了',newValue,oldValue)
})
这里可以看出一个问题,把属性对象直接传入watch监听,它可以监听到对象里每一个属性的变化,但是如果整个对象发生改变,也就是给它赋值一个新的对象,它是无法监听的。
(2)箭头函数包裹属性对象(推荐)
除了直接监听,还可以像基本类型那样先包裹一个箭头函数,再进行监听:
watch(() => person.car,(newValue,oldValue)=>{
console.log('person.car变化了',newValue,oldValue)
})
包裹了箭头函数之后,它反而无法监听里面的属性变化,它只关注对象的地址是否发生改变。如果想既监听内部属性,也监听对象地址,只需要再加上深度监听属性即可,即设置deep:true。
watch(() => person.car,(newValue,oldValue)=>{
console.log('person.car变化了',newValue,oldValue)
}, {deep: true})
5.5.3 示例代码
请打开F12控制台观看打印输出信息。
5.6 监听多个数据
Vue3它可以同时监听多个数据,不需要分开写多个watch,可以写在一个watch里,将需要监听的属性放进数组里传入watch监听即可。
这里还是举上述的例子,这次监听的是名字和汽车这两个属性,但不监听年龄属性:
let person = reactive({
name:'张三',
age:18,
car:{
c1:'奔驰',
c2:'宝马'
}
})
function changeC1(){
person.car.c1 = '奥迪'
}
function changeC2(){
person.car.c2 = '大众'
}
function changeCar(){
person.car = {c1:'雅迪',c2:'爱玛'}
}
watch([()=>person.name,person.car],(newValue,oldValue)=>{
console.log('person.car变化了',newValue,oldValue)
})
注意:因为你是同时监听多个属性,因此里面只要某一个属性发生了变化,它都会触发。你传入的是数组,因此它也会返回整个数组,里面就是各个属性的值。
请打开F12控制台观看打印输出信息。
5.7 解除watch监听
我们一旦使用了watch,它就会永久监听,我们同样有办法将这个监听解除掉。
在watch调用的时候,实际上它是有返回值的,我们可以使用变量接收并打印观察一下:
const stopWatch = watch(sum,(newValue,oldValue)=>{
......
})
console.log(stopWatch)
我们发现它其实是一个函数来的,如果我们想终止监听,只需要执行它即可。
举一个例子,假设sum从0开始累加1,当大于10的时候取消监听。
<template>
<div class="person">
<h2>当前求和为:{{sum}}</h2>
<button @click="changeSum">点我sum+1</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {ref,watch} from 'vue'
// 数据
let sum = ref(0)
// 方法
function changeSum(){
sum.value += 1
}
const stopWatch = watch(sum,(newValue,oldValue)=>{
console.log('sum变化了',newValue,oldValue)
if(newValue >= 10){
stopWatch()
}
})
</script>
请打开F12控制台观看打印输出信息。
5.8 watchEffect
5.8.1 watchEffect与watch的区别
1、都能监听响应式数据的变化,不同的是监听数据变化的方式不同
2、watch:要明确指出监听的数据,它是一个惰性监听,你不指定它就不监听
3、watchEffect:不用明确指出监听的数据(函数中用到哪些属性,那就监听哪些属性)。
5.8.2 watchEffect的使用
我们同样用一个简单的例子做一个比较,假设有一个需求:当水温达到60度,或水位达到80cm时,给服务器发请求。
1、watch实现方式
watch有多种监听方式,可以对水温和水位分别监听,也可以同时监听。这里我们使用最快捷的方式,使用数组同时监听俩属性:
watch([temp,height],(value)=>{
// 从value中获取最新的水温(newTemp)、最新的水位(newHeight)
let [newTemp,newHeight] = value
// 逻辑
if(newTemp >= 60 || newHeight >= 80){
console.log('给服务器发请求')
}
})
在这个需求里,涉及到的变量总共是两个,分别是水温和水位,因此watch需要监听这两个属性才能实现。如果需求很复杂,涉及的变量有10个,那么你也只能老老实实的给watch指定这10个变量的监听。
2、watchEffect实现方式
watchEffect它不需要你去指定具体监听的属性,你只需要关注业务逻辑,只要代码涉及到的变量,它会帮你自动监听。
如果用watchEffect来实现上面的需求,将会省去所有监听的属性,变得更加简洁和灵活。
watchEffect(()=>{
if(temp.value >= 60 || height.value >= 80){
console.log('给服务器发请求')
}
})
3、watchEffect的瑕疵
如果在if条件里使用了或运算符||,它会按照条件顺序进行监听,一旦前面的条件符合,则后续的条件将不会监听。
继续用上面的例子:当水温达到60度,或水位达到80cm时,给服务器发请求。我们的代码写成if(temp.value >= 60 || height.value >= 80),按照我们的理解,无论是temp还是height变量,只要哪一个条件符合它都会触发。
但watchEffect的机制并非如此,它认为第一个条件是temp是否大于60,如果符合,则height的值无论是多少它都不关心。它只有在第一个条件不符合的情况下,它才会关注第二个条件。
因此大家在使用watchEffect写逻辑的时候,要小心使用||运算符,如果实在想在watchEffect实现if的或运算符,可以多写一个if来巧妙的绕开或运算符。
watchEffect(()=>{
if (temp.value >= 60) {
console.log('给服务器发请求')
}
if (height.value >= 80 && temp.value < 60) {
console.log('给服务器发请求')
}
})
上面的代码将if(temp.value >= 60 || height.value >= 80)拆分成两段,如果水温temp大于60就发请求,如果水位超过80且水温不到60也发请求(因为水温到60就不用考虑水位了)。这样也同样能完美实现我们的需求。
5.8.3 watchEffect的注意事项
1、watchEffect是立即监听的,相当于watch配置了immediate为true一样
2、watchEffect无论是基本类型还是对象类型,它都可以监听到
3、watchEffect的解除监听方式跟watch是一样的
5.8.4 示例代码
请打开F12控制台观看打印输出信息。
原文链接:https://juejin.cn/post/7332762964376616960 作者:sowhat88