讲完计算属性也该讲监视器了,VUE3的监视器和VUE2的监视器的作用一样,都是监视数据,当数据改变时会做出相应的操作。
VUE3的监视器
VUE3的监视器只能监视以下四种数据,记住了只有下面四种数据是能够被监视的:
ref
定义的响应式数据。reactive
定义的响应式数据。- 函数返回的值。
- 包含以上三种的数组。
想使用监视器watch
,按照前面几个知识点的惯例,肯定是先引入了。
html
<script setup lang="ts">
import { watch } from "vue";
</script>
然后就是watch
的用法:那么怎么监视呢?但凡看过前几篇帖子的人都知道,不用想watch
肯定又是一个函数,然后我们要往里面传参数,watch
函数里面接收三个参数:
watch(source, callback, options)
source
:就是要监视的数据。callback(newValue,oldValue)
:监视数据的回调函数,该回调函数也有两个参数。
newValue
:监视数据的新值。oldValue
:监视数据的旧值。options
:一些配置比如deep
,immediate
等等。- 返回值:关闭这个临时监视器的方法。
至于接下来怎么具体使用,我会对这四种数据一一讲解。
监视ref定义的基本类型响应式数据
监视ref
定义的基本类型
响应式数据:直接写数据名就好,不用在后面加.value
,因为我们是监视ref
定义的响应式数据。
先做一个修改年龄的小功能:
html
<template>
<div>姓名:{{ name }}</div>
<br />
<div>年龄:{{ age }}</div>
<br />
<div><button @click="changeAge">修改年龄</button></div>
<br />
</template>
<script setup lang="ts">
import { watch, ref } from "vue";
let name = ref("月亮");
let age = ref(18);
function changeAge() {
age.value++;
}
</script>
<style scoped>
然后就是给年龄数据加上监视器:
html
<template>
<div>姓名:{{ name }}</div>
<br />
<div>年龄:{{ age }}</div>
<br />
<div><button @click="changeAge">修改年龄</button></div>
<br />
</template>
<script setup lang="ts">
import { watch, ref } from "vue";
let name = ref("月亮");
let age = ref(18);
function changeAge() {
age.value++;
}
// 监视器
watch(age, (value) => {
console.log("此时年龄为" + value);
});
</script>

这里需要注意的点:
注意点1
监视器监视ref
定义的基本类型
响应式数据的时候不需要写.value
,因为我们前面说在ts
或者js
里操作ref
定义的数据的时候需要加上.value
,但是监视器这里不需要。记得我们前面说的监视器所能监视的四种数据,第一条讲的就是监视ref
定义的响应式数据,不是ref
定义的响应式数据的value
属性。
注意点2
监视器的回调函数里面的两个参数,一个是新值,一个是旧值,这里的值指的是.value
以后的值,不是ref
定义的RefImpl
对象。
监视ref定义的复杂类型响应式数据
当然先是去声明一个复杂类型的响应式数据啦。声明一个person
对象,里面有两个属性name
和age
。
html
<template>
<div>姓名:{{ person.name }}</div>
<br />
<div>年龄:{{ person.age }}</div>
<br />
<div><button @click="changeName">修改名字</button></div>
<div><button @click="changeAge">修改年龄</button></div>
<div><button @click="changePerson">修改整个人</button></div>
<br />
</template>
<script setup lang="ts">
import { watch, ref } from "vue";
let person = ref({
name: "月亮",
age: 18,
});
function changeName() {
person.value.name += "~";
}
function changeAge() {
person.value.age++;
}
function changePerson() {
person.value = {
name: "yueliang",
age: 1,
};
}
</script>

然后就去监视person
这个对象。
js
watch(person, () => {
console.log("触发了watch");
});
此时修改名字name
,会发现没有触发这个监视器。

然后我们再去修改年龄age
,会发现也没有触发这个监视器。

最后我们去修改整个人也就是person
对象,会发现这次才会触发这个监视器。

这一点和VUE2的监视器watch
原理是一样的,因为监视器监视的数据是一个对象的时候,监视的是这个对象person
的地址,单纯的修改名字name
或者修改年龄age
是不会修改这个person
对象的地址的,所以也就不会去触发这个监视器,只有在修改整个对象的时候才会修改对象的地址,才会去触发监视器。
那么我们怎么做才能在只修改对象的某个属性的时候就触发监视器呢?其实也跟VUE2也一样,加上一个配置属性deep
设置为true
开启深度监听就好了,我们前面说watch
接收三个参数,第三个参数options
就是配置对象,载里面加上deep
属性就好了。
html
<template>
<div>姓名:{{ person.name }}</div>
<br />
<div>年龄:{{ person.age }}</div>
<br />
<div><button @click="changeName">修改名字</button></div>
<div><button @click="changeAge">修改年龄</button></div>
<div><button @click="changePerson">修改整个人</button></div>
<br />
</template>
<script setup lang="ts">
import { watch, ref } from "vue";
let person = ref({
name: "月亮",
age: 18,
});
function changeName() {
person.value.name += "~";
}
function changeAge() {
person.value.age++;
}
function changePerson() {
person.value = {
name: "yueliang",
age: 1,
};
}
watch(
person,
() => {
console.log("触发了watch");
},
{ deep: true }
);
</script>
此时就可以在只修改某个属性的时候就可以触发监视器了,我们修改名字试一下。

发现效果是可行的。
常用的配置属性还有一个,是immediate
,表示立即触发一次,懂得VUE2的人自然都懂。
js
watch(
person,
() => {
console.log("触发了watch");
},
{ deep: true, immediate: true }
);
它会立即触发一次监视器,这里刚打开页面什么都没改,就触发了一次监视器。

注意点1
监视器监视ref
定义的复杂类型
响应式数据的时候也不需要写.value
。
注意点2
这里有一个注意的点,就是我们在修改对象的属性的时候,回调函数的两个属性newValue
和oldValue
值是一样的且都是Proxy
对象,这里我们点击修改名字按钮去试一下。
js
watch(
person,
(newValue, oldValue) => {
console.log(newValue, oldValue);
console.log("触发了watch");
},
{ deep: true }
);

值一样应该很好理解吧,因为只是修改对象的属性,没有修改地址,而监视器监视的又是地址,所以newValue
和oldValue
的值是一样的,要不是因为加了deep
属性,压根就不会触发监视器。至于为什么值是一个Proxy
对象,在讲监视ref
定义的复杂类型响应式数据的时候最后说了,监视的数据是一个RefImpl
对象,而newValue
和oldValue
是它的value
值,而ref
定义的复杂数据类型的value
就是一个Proxy
对象。
只有在修改整个对象的时候newValue
和oldValue
的值才是不一样的,这里我们去点击修改整个人按钮试一下。

因为此时整个对象都被修改了,地址也就改变了,所以newValue
和oldValue
的值不一样。
注意点3
我们在配置immediate
属性以后再打印一下newValue
和oldValue
的值。
js
watch(
person,
(newValue, oldValue) => {
console.log(newValue, oldValue);
console.log("触发了watch");
},
{ deep: true, immediate: true }
);
按照我们预想的,应该是打印两个一样的值对吧,其实不是的。

会发现newValue
是对象的值,而oldValue
是undefined
,immediate
的立即触发是因为person
对象原本是没有值的也就是undefined
,然后后面赋值了才会触发。就相当于person
对象的前身也就是没有赋值的时候是undefined
。这么理解就行了我也说不好。
题外话
至于还有说监视person.name
的,别忘了,最开始就说了watch
只能监视的四种属性,person.name
不属于其中任何一个,而且这么写是会报错的。至于怎么监视后面会说。
监视reactive定义的复杂类型响应式数据
前面讲完了如何监视ref
定义的响应式数据,现在讲一下如何监视reactive
定义的复杂类型响应式数据。
我们依旧先把前面的变量声明好:
html
<template>
<div>姓名:{{ person.name }}</div>
<br />
<div>年龄:{{ person.age }}</div>
<br />
<div><button @click="changeName">修改名字</button></div>
<div><button @click="changeAge">修改年龄</button></div>
<div><button @click="changePerson">修改整个人</button></div>
<br />
</template>
<script setup lang="ts">
import { watch, reactive } from "vue";
let person = reactive({
name: "月亮",
age: 18,
});
function changeName() {
person.name += "~";
}
function changeAge() {
person.age++;
}
function changePerson() {
// 我倒要看看有没有人问为什么这么修改,前面文章已经讲过了
Object.assign(person, { name: "yueliang", age: 1 });
}
</script>
监视reactive
定义的复杂类型响应式数据用法和监视ref
的用法差不多,都是往watch
函数里面去传值,第一个是监视数据,第二个是回调函数,第三个是配置对象。
js
watch(person, (newValue, oldValue) => {
console.log(newValue, oldValue);
});
原本页面展示:

然后我们依次点击三个按钮,看看会不会触发监视器,触发以后又打印什么东西。

会发现这三个改变都触发了监视器,而且这三个改变,其实都没有改变监视对象的地址,监视器也没有加上deep
配置属性,结果还是触发了监视器,按照我们讲ref
的时候,在修改监视对象的内部属性的时候,如果不加上deep
配置属性,是不会触发监视器的,但是监视reactive
的时候是不用加上deep
配置属性就可以在修改内部属性的时候就触发监视器的,也能理解,毕竟reactive
定义的复杂数据类型只能修改内部属性,没法整个修改对象(不明白的去看上篇文章)。所以监视reactive
定义的复杂类型数据的时候是默认开启deep
配置属性的,且不可关闭 。至于newValue
和oldValue
为啥是一样的,看ref
去,一个意思。
注意点1
监视器监视reactive
定义的复杂类型
响应式数据的时候依旧不需要写.value
,emmmreactive
本来也不需要写.value
。
注意点2
监视器监视reactive
定义的复杂类型
响应式数据的时候默认开启深度监听且不能关闭。
注意点3
监视器监视reactive
定义的复杂类型
响应式数据的时候newValue
和oldValue
是一样的,因为都没有改变对象地址,而且都是Proxy
对象
监视函数返回的值
还记得我们前面说的如何监视对象内部的属性吗,前面讲监视person.name
的时候不能直接把person.name
当作监视数据,因为这不属于watch
监视的四种数据之一,所以会报错,那么接下来我们就讲一下如何监视对象内部的数据。
我们要想监视对象内部的属性,就得把它写成函数的返回值形式。而且这还得分情况:
- 如果属性不是一个对象类型,需要写成函数形式。
- 如果属性是一个对象类型,可以直接写,也可以写成函数形式,当然更推荐写成函数形式。
老规矩先去声明一个变量出来:
html
<template>
<div>姓名:{{ person.name }}</div>
<br />
<div>年龄:{{ person.age }}</div>
<br />
<div>代步工具:{{ person.car.c1 }} {{ person.car.c2 }}</div>
<br />
<div><button @click="changeName">修改名字</button></div>
<div><button @click="changeAge">修改年龄</button></div>
<div><button @click="changeC1">修改第一台车</button></div>
<div><button @click="changeC2">修改第二台车</button></div>
<div><button @click="changeCar">修改全部车</button></div>
<div><button @click="changePerson">修改整个人</button></div>
<br />
</template>
<script setup lang="ts">
import { watch, reactive } from "vue";
let person = reactive({
name: "月亮",
age: 18,
car: {
c1: "奔驰",
c2: "沃尔沃",
},
});
function changeName() {
person.name += "~";
}
function changeAge() {
person.age++;
}
function changeC1() {
person.car.c1 = "benchi";
}
function changeC2() {
person.car.c2 = "woerwo";
}
function changeCar() {
person.car = {
c1: "雅迪",
c2: "爱玛",
};
}
function changePerson() {
Object.assign(person, { name: "yueliang", age: 1 });
}
</script>

声明完变量了,然后测试这些修改按钮也是可以正常实现功能的,现在就是添加监视器了。
因为前面说了,监视对象内部属性有两种情况,一个是属性不为对象,一个是属性为对象,所以我们监视一个姓名person.name
,监视一个代步工具person.car
。
监视一个函数的返回值,那函数有返回值其实也就是一个getter
函数。
对象的属性不是一个对象
先监视一下不是对象的属性,比如person.name
,那怎么把person.name
转换成getter
函数呢,其实很简单,就是一个函数有一个返回值,也就是() => person.name
。
js
watch(
() => person.name,
(newValue, oldValue) => {
console.log("修改了person.name", newValue, oldValue);
}
);

从上面图可以看到,我们修改了很多属性,但是只有修改person.name
的时候才会触发监视器,这就成功实现了监视对象某个属性的效果,而且监视的对象的属性不是一个对象的话,newValue
和oldValue
是不一样的,就像是监听一个基本数据类型一样。
注意点1
监视的对属性不是一个对象的时候,只能封装成getter
函数的形式。
注意点2
监视的对属性不是一个对象的时候,就相当于监视ref
定义的基本类型数据,newValue
和oldValue
是不一样的,而且也是普通的值,不是RefImp
对象或者Proxy
对象。
对象的属性依旧是一个对象
现在监视是对象的属性,比如person.car
,前面说了监视的属性是一个对象类型,可以直接写,也可以写成函数形式。
写成函数形式
js
watch(
() => person.car,
(newValue, oldValue) => {
console.log("修改了person.car", newValue, oldValue);
}
);

这个图可能不一定看的出来,我也懒得上动图,结果其实就是在修改第一台车或者修改第二台车的时候,是不会触发监视器的,只有修改全部车的时候才会触发,其实这一点就和监视一个ref
定义的复杂类型数据是一样的了,只有修改地址的时候才会触发。如果加上一个deep
配置属性,才会在修改第一台车或者修改第二台车的时候触发监视器。
注意点1
对象的属性依旧是一个对象,用getter
函数监视的时候,就跟监视ref
定义的复杂类型的数据一样,不开启deep
的时候只有在修改整个对象的时候才会触发监视器,此时newValue
和oldValue
是不一样的且都是Proxy
对象。如果开启了deep
,在修改对象的某一个值得时候就可以触发监视器,newValue
和oldValue
是一样的且都是Proxy
对象。
直接写的形式
js
watch(person.car, (newValue, oldValue) => {
console.log("修改了person.car", newValue, oldValue);
});

这个图也不一定看的出来,其实就是我在修改第一台车和修改第二台车的时候触发了监视器,但是修改全部车的时候就没有触发监视器了。
注意点1
对象的属性依旧是一个对象,用直接写的形式监视的时候,只有修改其内部的属性的时候才会触发监视器,修改整个对象是不会触发监视器的,而且跟deep
无关。
注意点2
修改整个对象以后,再想修改其内部的属性,会发现有效,但是也无法触发监视器了。所以不推荐直接写,推荐使用getter
函数的形式。
结论
监视对象内部属性的时候,不用管这个属性是不是对象,都直接使用getter
函数的形式去监视。
监视上述的多个数据
除了可见监视上述的三种数据之外,还可以监视包含上述三种数据的数组。
老规矩,先声明变量:
html
<template>
<div>姓名:{{ person.name }}</div>
<br />
<div>年龄:{{ person.age }}</div>
<br />
<div>代步工具:{{ person.car.c1 }} {{ person.car.c2 }}</div>
<br />
<div><button @click="changeName">修改名字</button></div>
<div><button @click="changeAge">修改年龄</button></div>
<div><button @click="changeC1">修改第一台车</button></div>
<div><button @click="changeC2">修改第二台车</button></div>
<div><button @click="changeCar">修改全部车</button></div>
<div><button @click="changePerson">修改整个人</button></div>
<br />
</template>
<script setup lang="ts">
import { watch, reactive } from "vue";
let person = reactive({
name: "月亮",
age: 18,
car: {
c1: "奔驰",
c2: "沃尔沃",
},
});
function changeName() {
person.name += "~";
}
function changeAge() {
person.age++;
}
function changeC1() {
person.car.c1 = "benchi";
}
function changeC2() {
person.car.c2 = "woerwo";
}
function changeCar() {
person.car = {
c1: "雅迪",
c2: "爱玛",
};
}
function changePerson() {
Object.assign(person, { name: "yueliang", age: 1 });
}
</script>
比如我们去监视名字和车:
js
watch([() => person.name, () => person.car], (newValue, oldValue) => {
console.log("修改了person", newValue, oldValue);
});

图片看不出来,修改名字的时候是可以触发监视器的,修改第一辆车或者修改第二辆车的时候不会触发,修改全部车的时候才会触发,其实也就跟前面说的一样,只是合并再一起了而已,然后newValue
和oldValue
也是对应的集合。在开启deep
以后修改第一辆车或者第二辆车就可以触发监视器了。
关闭监视器
前面说了监视器如何使用,那么又该怎么关闭监视器呢?
VUE2中的监视器是一个选项,此时是没法去关闭监视器的,但是有一个once
配置可以让监视器只执行一次,想要关闭监视器就得使用到this.$watch
方法。
VUE2中通过this.$watch
方法去创建一个临时监视器。
this.$watch(source, callback, options)
source
:就是要监视的数据,写成字符串形式。callback(newValue,oldValue)
:监视数据的回调函数,该回调函数也有两个参数。
newValue
:监视数据的新值。oldValue
:监视数据的旧值。options
:一些配置比如deep
,immediate
等等。- 返回值:关闭这个临时监视器的方法。
是不是发现这个东西其实和VUE3的watch
一样,我感觉VUE3的监视器就是把VUE2的this.$watch
搬过来改了一下,看清楚啊,this.$watch
方法写监视的数据的时候是写成字符串形式的,跟VUE3的watch
不一样。
html
<template>
<div>姓名:{{ name }}</div>
<br />
<div>年龄:{{ age }}</div>
<br />
<div><button @click="changeAge">修改年龄</button></div>
<br />
</template>
<script lang="ts">
export default {
data() {
return {
name: "月亮",
age: 1,
};
},
created() {
const unWatch = this.$watch("age", function (value) {
console.log("此时年龄为" + value);
if (value >= 10) {
unWatch();
}
});
},
methods: {
changeAge() {
this.age++;
},
},
};
</script>

VUE3关闭监视器也差不多如此,也是调用监视器的返回值。
html
<template>
<div>姓名:{{ name }}</div>
<br />
<div>年龄:{{ age }}</div>
<br />
<div><button @click="changeAge">修改年龄</button></div>
<br />
</template>
<script setup lang="ts">
import { watch, ref } from "vue";
let name = ref("月亮");
let age = ref(1);
function changeAge() {
age.value++;
}
// 监视器
let unWatch = watch(age, (value) => {
console.log("此时年龄为" + value);
if (value >= 10) {
unWatch();
}
});
</script>
