监视器

讲完计算属性也该讲监视器了,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:一些配置比如deepimmediate等等。
  • 返回值:关闭这个临时监视器的方法。

至于接下来怎么具体使用,我会对这四种数据一一讲解。

监视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对象,里面有两个属性nameage

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

这里有一个注意的点,就是我们在修改对象的属性的时候,回调函数的两个属性newValueoldValue值是一样的且都是Proxy对象,这里我们点击修改名字按钮去试一下。

js 复制代码
watch(
  person,
  (newValue, oldValue) => {
    console.log(newValue, oldValue);
    console.log("触发了watch");
  },
  { deep: true }
);

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

只有在修改整个对象的时候newValueoldValue的值才是不一样的,这里我们去点击修改整个人按钮试一下。

因为此时整个对象都被修改了,地址也就改变了,所以newValueoldValue的值不一样。

注意点3

我们在配置immediate属性以后再打印一下newValueoldValue的值。

js 复制代码
watch(
  person,
  (newValue, oldValue) => {
    console.log(newValue, oldValue);
    console.log("触发了watch");
  },
  { deep: true, immediate: true }
);

按照我们预想的,应该是打印两个一样的值对吧,其实不是的。

会发现newValue是对象的值,而oldValueundefinedimmediate的立即触发是因为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配置属性的,且不可关闭 。至于newValueoldValue为啥是一样的,看ref去,一个意思。

注意点1

监视器监视reactive定义的复杂类型响应式数据的时候依旧不需要写.value,emmmreactive本来也不需要写.value

注意点2

监视器监视reactive定义的复杂类型响应式数据的时候默认开启深度监听且不能关闭。

注意点3

监视器监视reactive定义的复杂类型响应式数据的时候newValueoldValue是一样的,因为都没有改变对象地址,而且都是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的时候才会触发监视器,这就成功实现了监视对象某个属性的效果,而且监视的对象的属性不是一个对象的话,newValueoldValue是不一样的,就像是监听一个基本数据类型一样。

注意点1

监视的对属性不是一个对象的时候,只能封装成getter函数的形式。

注意点2

监视的对属性不是一个对象的时候,就相当于监视ref定义的基本类型数据,newValueoldValue是不一样的,而且也是普通的值,不是RefImp对象或者Proxy对象。

对象的属性依旧是一个对象

现在监视是对象的属性,比如person.car,前面说了监视的属性是一个对象类型,可以直接写,也可以写成函数形式。

写成函数形式

js 复制代码
watch(
  () => person.car,
  (newValue, oldValue) => {
    console.log("修改了person.car", newValue, oldValue);
  }
);

这个图可能不一定看的出来,我也懒得上动图,结果其实就是在修改第一台车或者修改第二台车的时候,是不会触发监视器的,只有修改全部车的时候才会触发,其实这一点就和监视一个ref定义的复杂类型数据是一样的了,只有修改地址的时候才会触发。如果加上一个deep配置属性,才会在修改第一台车或者修改第二台车的时候触发监视器。

注意点1

对象的属性依旧是一个对象,用getter函数监视的时候,就跟监视ref定义的复杂类型的数据一样,不开启deep的时候只有在修改整个对象的时候才会触发监视器,此时newValueoldValue是不一样的且都是Proxy对象。如果开启了deep,在修改对象的某一个值得时候就可以触发监视器,newValueoldValue是一样的且都是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);
});

图片看不出来,修改名字的时候是可以触发监视器的,修改第一辆车或者修改第二辆车的时候不会触发,修改全部车的时候才会触发,其实也就跟前面说的一样,只是合并再一起了而已,然后newValueoldValue也是对应的集合。在开启deep以后修改第一辆车或者第二辆车就可以触发监视器了。

关闭监视器

前面说了监视器如何使用,那么又该怎么关闭监视器呢?

VUE2中的监视器是一个选项,此时是没法去关闭监视器的,但是有一个once配置可以让监视器只执行一次,想要关闭监视器就得使用到this.$watch方法。

VUE2中通过this.$watch方法去创建一个临时监视器。

this.$watch(source, callback, options)

  • source:就是要监视的数据,写成字符串形式。
  • callback(newValue,oldValue):监视数据的回调函数,该回调函数也有两个参数。
    • newValue:监视数据的新值。
    • oldValue:监视数据的旧值。
  • options:一些配置比如deepimmediate等等。
  • 返回值:关闭这个临时监视器的方法。

是不是发现这个东西其实和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>
相关推荐
David凉宸11 分钟前
凉宸推荐给大家的一些开源项目
前端
袋鱼不重13 分钟前
Cursor 最简易上手体验:谷歌浏览器插件开发3s搞定!
前端·后端·cursor
hyyyyy!13 分钟前
《从分遗产说起:JS 原型与继承详解》
前端·javascript·原型模式
竹苓14 分钟前
从一个想法到上线,一万字记录我开发浏览器插件的全过程
前端
小桥风满袖15 分钟前
Three.js-硬要自学系列19 (曲线颜色渐变、渐变插值、查看设置gltf顶点、山脉高度可视化)
前端·css·three.js
zayyo15 分钟前
Vue.js性能优化新思路:轻量级SSR方案深度解析
前端·面试·性能优化
北溟鱼鱼鱼16 分钟前
跨域解决方案
前端
六边形66616 分钟前
一文搞懂JavaScript 与 BOM、DOM、ECMAScript、Node.js的用处
前端·javascript·面试
snakeshe101017 分钟前
React 中的 UpdateQueue 详解
前端
佛系菜狗21 分钟前
鸿蒙OSS文件(视频/图片)压缩上传组件-能够增删改查
前端·鸿蒙