一、详细信息

1、watch():

侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。默认是懒侦听的,即仅在侦听源发生变化时才执行回调函数。

2、watch() 中三个参数。
  • 第一个参数是侦听器的源。这个来源可以是以下几种:
  • 一个函数,返回一个值
  • 一个 ref
  • 一个响应式对象
  • ...或是由以上类型的值组成的数组
  • 第二个参数是在发生变化时要调用的回调函数。
  • 这个回调函数接受三个参数:新值、旧值,以及一个用于注册副作用清理的回调函数。该回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用,例如等待中的异步请求。
  • 当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值。
  • 第三个可选的参数是一个对象,支持以下这些选项:
  • immediate:在侦听器创建时立即触发回调。第一次调用时旧值是 undefined
  • deep:如果源是对象,强制深度遍历,以便在深层级变更时触发回调。参考深层侦听器。
  • flush:调整回调函数的刷新时机。参考回调的刷新时机及 watchEffect()。
  • onTrack / onTrigger:调试侦听器的依赖。参考调试侦听器。
  • once:回调函数只会运行一次。侦听器将在回调函数首次运行后自动停止。 
3、与 watchEffect() 相比,watch() 使我们可以:

(1)懒执行副作用;

(2)更加明确是应该由哪个状态触发侦听器重新执行;

(3)可以访问所侦听状态的前一个值和当前值。

二、使用场景

1、监视ref定义的【基本类型】数据:直接写数据名即可,监视的是其value值的改变。
<template>
  <div class="person">
    <h2>年龄:{{ age }}</h2>
    <button @click="incrAge">年龄递增</button>
  </div>
</template>


<!--npm i vite-plugin-vue-setup-extend -D-->
<!-- 然后就可以直接在 script 标签中写组件名称 -->
<script lang="ts" setup name="Person">
import {ref, watch} from "vue";

  // 数据
  let age = ref(18)

  // 方法
  function incrAge() {
    console.log('age === ', age)
    age.value += 1
  }

  // 监听  =>  返回值是停止监听器,调用后就能移除 age 上的监听
  let stopWatch = watch(age, (newValue, oldValue) => {
    console.log('监听到 age 发生变化', oldValue + ' ==> ' + newValue)
    if (newValue >= 25) {
      // 移除监听
      stopWatch()
    }
  });

</script>


<style lang="scss">
  .person {
    background-color: pink;
    box-shadow: 0 0 10px;
    border-radius: 10px;
    padding: 20px;
    margin: 100px;
  }
  button {
    margin: 0 5px;
  }
  li {
    font-size: 20px;
  }
</style>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
2、监视ref定义的【对象类型】数据:直接写数据名,此时监视的是对象的【地址值】,若想监视对象内部的数据,要手动开启深度监视。

注意:

  • 若修改的是ref定义的对象中的属性,newValueoldValue 都是新值,因为它们是同一个对象。
  • 若修改整个ref定义的对象,newValue 是新值, oldValue 是旧值,因为不是同一个对象了。
<template>
  <div class="person">
    <h2>姓名:{{ person.name }}</h2>
    <h2>年龄:{{ person.age }}</h2>
    <button @click="modifyName">修改姓名</button>
    <button @click="incrAge">年龄递增</button>
    <button @click="modifyPerson">修改整个人信息</button>
  </div>
</template>


<!--npm i vite-plugin-vue-setup-extend -D-->
<!-- 然后就可以直接在 script 标签中写组件名称 -->
<script lang="ts" setup name="Person">
import {ref, watch} from "vue";

  // 数据
  let person = ref({
    name: '张三',
    age: 18
  })

  // 方法
  function modifyName() {
    person.value.name += '#'
  }
  function incrAge() {
    person.value.age += 1
  }
  function modifyPerson() {
    person.value = { name: '李四', age: 30 }
  }

  // 监听
  // 默认监听的是 person 对象的地址,修改其中的 name 或 age 都不会触发监听
  /*watch(person, (newVale, oldValue) => {
    console.log('监听到 person 发生了变化', newVale, oldValue)
  })*/

  // 可通过配置 'deep[深层监听]' 来实现监听 person 中的 name 或 age。控制台信息如下:
  // 修改姓名:监听到 person 发生了变化 Proxy(Object) {name: '张三#', age: 18} Proxy(Object) {name: '张三#', age: 18}
  // 年龄递增:监听到 person 发生了变化 Proxy(Object) {name: '张三', age: 19} Proxy(Object) {name: '张三', age: 19}
  // 修改整个人信息:监听到 person 发生了变化 Proxy(Object) {name: '李四', age: 30} Proxy(Object) {name: '张三', age: 19}
  // 注意:此时,修改某个属性时,旧值和新值相同,因为它们读取的数据源相同。修改整个对象时,旧值和新值不相同,因为它的地址已经发生了变化,读取的数据源不一致。
  watch(person, (newVale, oldValue) => {
    console.log('监听到 person 发生了变化', newVale, oldValue)
  }, { deep: true })

</script>


<style lang="scss">
  .person {
    background-color: pink;
    box-shadow: 0 0 10px;
    border-radius: 10px;
    padding: 20px;
    margin: 100px;
  }
  button {
    margin: 0 5px;
  }
  li {
    font-size: 20px;
  }
</style>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
3、监视reactive定义的【对象类型】数据,且默认开启了深度监视。
<template>
  <div class="person">
    <h2>姓名:{{ person.name }}</h2>
    <h2>年龄:{{ person.age }}</h2>
    <button @click="modifyName">修改姓名</button>
    <button @click="incrAge">年龄递增</button>
    <button @click="modifyPerson">修改整个人信息</button>
  </div>
</template>


<!--npm i vite-plugin-vue-setup-extend -D-->
<!-- 然后就可以直接在 script 标签中写组件名称 -->
<script lang="ts" setup name="Person">
import {reactive, watch} from "vue";

  // 数据
  let person = reactive({
    name: '张三',
    age: 18
  })

  // 方法
  function modifyName() {
    person.name += '#'
  }
  function incrAge() {
    person.age += 1
  }
  function modifyPerson() {
    // 覆盖原本属性的值,不会改变原本对象的地址
    Object.assign(person, { name: '李四', age: 30 })
  }

  // 监听
  // 默认是开启深层监视的,所以修改其中的 name 或 age 也都会触发监听
  // 修改姓名:监听到 person 发生了变化 Proxy(Object) {name: '张三#', age: 18} Proxy(Object) {name: '张三#', age: 18}
  // 年龄递增:监听到 person 发生了变化 Proxy(Object) {name: '张三', age: 19} Proxy(Object) {name: '张三', age: 19}
  // 修改整个人信息:监听到 person 发生了变化 Proxy(Object) {name: '李四', age: 30} Proxy(Object) {name: '李四', age: 30}
  // 注意:reactive 不管是修改某个属性,还是整个对象,旧值和新值都相同,因为它们读取的数据源相同。Object.assign()也不会改变其原来的地址
  watch(person, (newVale, oldValue) => {
    console.log('监听到 person 发生了变化', newVale, oldValue)
  })

</script>


<style lang="scss">
  .person {
    background-color: pink;
    box-shadow: 0 0 10px;
    border-radius: 10px;
    padding: 20px;
    margin: 100px;
  }
  button {
    margin: 0 5px;
  }
  li {
    font-size: 20px;
  }
</style>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
4、监视refreactive定义的【对象类型】数据中的某个属性
<template>
  <div class="person">
    <h2>姓名:{{ person.name }}</h2>
    <h2>年龄:{{ person.age }}</h2>
    <h2>房子:{{ person.house.h1 }},{{ person.house.h2 }}</h2>
    <button @click="modifyName">修改姓名</button>
    <button @click="incrAge">年龄递增</button>
    <button @click="modifyHouseH1">装修第一栋房子</button>
    <button @click="modifyHouseH2">装修第二栋房子</button>
    <button @click="modifyHouse">装修所有的房子</button>
  </div>
</template>


<!--npm i vite-plugin-vue-setup-extend -D-->
<!-- 然后就可以直接在 script 标签中写组件名称 -->
<script lang="ts" setup name="Person">
import {reactive, watch} from "vue";

  // 数据
  let person = reactive({
    name: '张三',
    age: 18,
    house: {
      h1: '毛胚房',
      h2: '平房'
    }
  })

  // 方法
  function modifyName() {
    person.name += '#'
  }
  function incrAge() {
    person.age += 1
  }
  function modifyHouseH1() {
    person.house.h1 = '套房'
  }
  function modifyHouseH2() {
    person.house.h2 = '别墅'
  }
  function modifyHouse() {
    person.house = { h1: '套房1', h2: '别墅1' }
  }

  // 监听 - 对象中的基本数据类型
  // 修改姓名:监听到 person.name 发生了变化 张三# 张三
  // 旧值和新值不一致。
  watch(() => person.name, (newValue, oldValue) => {
    console.log('监听到 person.name 发生了变化', newValue, oldValue)
  })

  // 监听 - 对象中的对象类型
  // () => person.house 默认监听对象的地址。如下,此时修改 h1 或 h2 不会触发该监听,修改 house 则会触发该监听
  /*watch(() => person.house, (newValue, oldValue) => {
    console.log('监听到 person.house 发生了变化', newValue, oldValue)
  })*/

  // 可通过配置 deep: true 来实现,此时修改 h1 或 h2 或 house 时均会触发该监听器。
  // 装修第一栋房子:监听到 person.house 发生了变化 Proxy(Object) {h1: '套房', h2: '平房'} Proxy(Object) {h1: '套房', h2: '平房'}
  // 装修第二栋房子:监听到 person.house 发生了变化 Proxy(Object) {h1: '毛胚房', h2: '别墅'} Proxy(Object) {h1: '毛胚房', h2: '别墅'}
  // 装修所有房子:监听到 person.house 发生了变化 Proxy(Object) {h1: '套房1', h2: '别墅1'} Proxy(Object) {h1: '毛胚房', h2: '平房'}
  watch(() => person.house, (newValue, oldValue) => {
    console.log('监听到 person.house 发生了变化', newValue, oldValue)
  }, { deep: true })

  // 若不用函数形式,直接监听 person.house,则 reactive 默认开启深层监听。如下,此时修改 h1 或 h2 会触发该监听,修改 house 则不会触发该监听,因为 house 已经不是同一个对象
  /*watch(person.house, (newValue, oldValue) => {
    console.log('监听到 person.house 发生了变化', newValue, oldValue)
  })*/

</script>


<style lang="scss">
  .person {
    background-color: pink;
    box-shadow: 0 0 10px;
    border-radius: 10px;
    padding: 20px;
    margin: 100px;
  }
  button {
    margin: 0 5px;
  }
  li {
    font-size: 20px;
  }
</style>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
5、监视上述的多个数据
<template>
  <div class="person">
    <h2>姓名:{{ person.name }}</h2>
    <h2>年龄:{{ person.age }}</h2>
    <h2>房子:{{ person.house.h1 }},{{ person.house.h2 }}</h2>
    <button @click="modifyName">修改姓名</button>
    <button @click="incrAge">年龄递增</button>
    <button @click="modifyHouseH1">装修第一栋房子</button>
    <button @click="modifyHouseH2">装修第二栋房子</button>
    <button @click="modifyHouse">装修所有的房子</button>
  </div>
</template>


<!--npm i vite-plugin-vue-setup-extend -D-->
<!-- 然后就可以直接在 script 标签中写组件名称 -->
<script lang="ts" setup name="Person">
import {reactive, watch} from "vue";

  // 数据
  let person = reactive({
    name: '张三',
    age: 18,
    house: {
      h1: '毛胚房',
      h2: '平房'
    }
  })

  // 方法
  function modifyName() {
    person.name += '#'
  }
  function incrAge() {
    person.age += 1
  }
  function modifyHouseH1() {
    person.house.h1 = '套房'
  }
  function modifyHouseH2() {
    person.house.h2 = '别墅'
  }
  function modifyHouse() {
    person.house = { h1: '套房1', h2: '别墅1' }
  }

  // 监视多个数据
  // 修改姓名:监听到 person.name 或 person.house.h1 发生了变化 (2) ['张三#', '毛胚房'] (2) ['张三', '毛胚房']
  // 装修第一栋房子:监听到 person.name 或 person.house.h1 发生了变化 (2) ['张三', '套房'] (2) ['张三', '毛胚房']
  watch([() => person.name, () => person.house.h1], (newValue, oldValue) => {
    console.log('监听到 person.name 或 person.house.h1 发生了变化', newValue, oldValue)
  }, { deep: true })
  
  // 监视多个数据时,回到函数中可以用两个数组接收入参
  /*watch([() => person.name, () => person.house.h1], ([newPersonName, newPersonHouseH1], [oldPersonName, oldPersonHouseH1]) => {
    console.log('监听到 person.name 或 person.house.h1 发生了变化')
    console.log(newPersonName)
    console.log(oldPersonName)
    console.log(newPersonHouseH1)
    console.log(oldPersonHouseH1)
  }, { deep: true })*/

</script>


<style lang="scss">
  .person {
    background-color: pink;
    box-shadow: 0 0 10px;
    border-radius: 10px;
    padding: 20px;
    margin: 100px;
  }
  button {
    margin: 0 5px;
  }
  li {
    font-size: 20px;
  }
</style>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.