# Vue3 侦听器 watch

205 阅读6分钟

Vue3 侦听器 watch

上一节我们简单的介绍了一下 vue3 项目中的计算属性,这一节我们继续 vue3 的基础知识讲解。

这一节我们来说 vue3 的侦听器。

学过 vue2 的小伙伴们肯定学习过侦听器,主要是用来监听页面数据或者是路由的变化,来执行相应的操作,在 vue3里面呢,也有侦听器的用法,功能基本一样,换汤不换药的东西。 侦听器是常用的 Vue API 之一,它用于监听一个数据并在数据变动时做一些自定义逻辑,本文将先列举侦听器在 Vue 中的使用方式,然后再分析源码讲述为什么可以这样使用、以及侦听器的实现原理。下面我们稍微说一下侦听器。

watch 侦听器使用。

watch API 使用,至少需要指定两个参数: source 和 callback,其中 callback 被明确指定只能为函数,所以不同是用方式的差别其实只在 source 。

接下来我们看一下 vue3 侦听器的基本使用:

<template>
  <div>
    <h1>watch 侦听器</h1>
    <el-input v-model="num" />
    <br>
    <br>
    <el-button type="primary" @click="num++">num + 1</el-button>
  </div>
</template>
<script>
  import { watch, ref } from 'vue'
  export default {
    setup() {
      const num = ref(1)
      watch(num, (newVal, oldVal) => {
        console.log("新值:", newVal, "   旧值:", oldVal)
      })
      return { num, }
    }
  }
</script>
<style scoped>
  .el-input {
    width: 100px;
  }
</style>

上面的代码是页面上有一个数字,点击按钮一下,数字加一,然后侦听器侦听数字的变化,打印出侦听的最新值和老值。

在这里插入图片描述

OK。上边的案例就是 vue3 侦听器的简单案例,侦听器和计算属性一样,可以创建多个侦听器,这个是没有问题的,案例就不写了,和上一节讲的声明多个计算属性是一致的。如果有不明白的可以看一下我的上一篇博客。

上边我们说过这么一句话,watch API 至少需要指定两个参数: source 和 callback。通过上边的案例我们看到了, 确实是两个,source 是监听的数据,callback 是监听回调,那为啥说是至少呢?

对的,因为他还有第三个参数 —— 配置对象

在 vue2 里面,我们打开页面就像让侦听器立即执行,而不是在第一次数据改变的时候才开始执行,这时候有一个参数叫 immediate ,设置了这个参数,创建第一次就执行,所以说呢,vue3 同样可以使用。

上面的案例刷新执行的时候发现,在点击按钮之前,也就是 num 创建的时候,侦听器是没有执行的,所以说呢,加上 immediate 参数,就可以让侦听器立即执行操作。

<template>
  <div>
    <h1>watch 侦听器</h1>
    <el-input v-model="num" />
    <br>
    <br>
    <el-button type="primary" @click="num++">num + 1</el-button>
  </div>
</template>
<script>
  import { watch, ref } from 'vue'
  export default {
    setup() {
      const num = ref(1)
      watch(num, (newVal, oldVal) => {
        console.log("新值:", newVal, "   旧值:", oldVal)
      }, {
        immediate: true
      })
      return { num, }
    }
  }
</script>
<style scoped>
  .el-input {
    width: 100px;
  }
</style>

在这里插入图片描述 我们看到,刷新完页面,还没有点击按钮让 num 加一的,控制台就有数据打印了,为什么呢?就是因为我们加了 immediate 为 true,让侦听器立即执行。控制台输出最新的值也就是我们初始化的值1,老的值没有,所以输出了 undefined。

侦听器监听 reactive

上面说了侦听器侦听单个数据,他也可以用来侦听对象的变化。

<template>
  <div>
    <h1>watch 侦听器</h1>
    <el-input v-model="num.age" />
    <br>
    <br>
    <el-button type="primary" @click="num.age++">num + 1</el-button>
  </div>
</template>
<script>
  import { watch, ref, reactive } from 'vue'
  export default {
    setup() {
      const num = reactive({
        name: '我是𝒆𝒅.',
        age: 10
      })
      watch(num, (newVal, oldVal) => {
        console.log(newVal, oldVal)
      })
      return { num }
    }
  }
</script>
<style scoped>
  .el-input {
    width: 100px;
  }
</style>

比如说上面代码,我们侦听 num 这个对象的变化。

在这里插入图片描述 看效果我们发下,在监听整个 reactive 响应式对象的时候,确实当里面的属性值发生改变了之后可以被侦听器检测到,但是 newVal 和 oldVal 的值都是新的,默认是10,点击之后,新值是 11 很正常,但是老值不应该是 10 吗?为什么这里老值和新值一样也是 11 呢?

这个不需要疑问哈,如果监听整个 reactive 数据的话,只能回调到最新的值,获取不到老的值。

那问题来喽,我就修改 age 属性,我就要获取 age 老的值怎么办?其实我们只需要监听 num 下面的 age 就可以了,先看下面的代码哈。

<template>
  <div>
    <h1>watch 侦听器</h1>
    <el-input v-model="num.age" />
    <br>
    <br>
    <el-button type="primary" @click="num.age++">num + 1</el-button>
  </div>
</template>
<script>
  import { watch, ref, reactive } from 'vue'
  export default {
    setup() {
      const num = reactive({
        name: '我是𝒆𝒅.',
        age: 10
      })
      watch(num.age, (newVal, oldVal) => {
        console.log(newVal, oldVal)
      })
      return { num }
    }
  }
</script>
<style scoped>
  .el-input {
    width: 100px;
  }
</style>

我们监听对象直接是 num.age, 监听年龄属性值,保存看一下效果。

在这里插入图片描述 刷新结果我们可以看到哈,我们啥都没干,侦听器直接报了一个警告给我们,啥意思呢,其实不能直接这样监听。

当我们需要监听某个对象属性的时候,我们不能直接对象点属性的方式进行监听,需要传入一个 getter 方法,也就是箭头函数进行监听,下面的代码是正确方式哈。

<template>
  <div>
    <h1>watch 侦听器</h1>
    <el-input v-model="num.age" />
    <br>
    <br>
    <el-button type="primary" @click="num.age++">num + 1</el-button>
  </div>
</template>
<script>
  import { watch, ref, reactive } from 'vue'
  export default {
    setup() {
      const num = reactive({
        name: '我是𝒆𝒅.',
        age: 10
      })
      watch(() => num.age, (newVal, oldVal) => {
        console.log(newVal, oldVal)
      })
      return { num }
    }
  }
</script>
<style scoped>
  .el-input {
    width: 100px;
  }
</style>

OK,保存刷新,我们发现,侦听器已经不报错了,而且我们点击按钮让 age 加一的时候,可以顺利的监听到 age 的变化,并且回调出最新值和上一次的值。

在这里插入图片描述

通过箭头函数,我们就可以实现对象属性的监听。

很多人说,vue2 在监听对象的时候需要对侦听器设置深度侦听,为什么 vue3 这个不需要呢?因为他监听响应式对象,默认就是深度监听。但是,如果监听的是深度嵌套对象或数组中的 property 变化时,仍然需要 deep 选项设置为 true。

看下面的案例,我们监听深层嵌套的 time 属性值。其实我觉得没大必要,不使用箭头函数其实可以。但是还是写一下吧。

<template>
  <div>
    <h1>watch 侦听器</h1>
    <el-input v-model="num.todo.time" />
    <br>
    <br>
    <el-button type="primary" @click="num.todo.time ++">num.todo.time + 1</el-button>
  </div>
</template>
<script>
  import { watch, ref, reactive, computed, } from 'vue'
  export default {
    setup() {
      const num = reactive({
        name: '我是𝒆𝒅.',
        age: 10,
        todo: {
          name: '弹吉他',
          time: 1
        }
      })
      watch(() => num, (newVal, oldVal) => {
        console.log(newVal.todo.time, oldVal.todo.time)
      })
      return { num }
    }
  }
</script>
<style scoped>
  .el-input {
    width: 100px;
  }
</style>

保存代码刷新,发现点击之后没有监听到。

在这里插入图片描述

这个时候就可以加上 deep 深度监听。

<template>
  <div>
    <h1>watch 侦听器</h1>
    <el-input v-model="num.todo.time" />
    <br>
    <br>
    <el-button type="primary" @click="num.todo.time ++">num.todo.time + 1</el-button>
  </div>
</template>
<script>
  import { watch, ref, reactive, computed, } from 'vue'
  export default {
    setup() {
      const num = reactive({
        name: '我是𝒆𝒅.',
        age: 10,
        todo: {
          name: '弹吉他',
          time: 1
        }
      })
      watch(() => num, (newVal, oldVal) => {
        console.log(newVal.todo.time, oldVal.todo.time)
      }, { deep: true })
      return { num }
    }
  }
</script>
<style scoped>
  .el-input {
    width: 100px;
  }
</style>

加上深度监听 { deep:true }

在这里插入图片描述 我们可以看到打印出信息来了,其实我觉得这个方法有点多余,但是万一用到呢是吧?啊哈哈哈哈,自己根据情况选择使用吧。

但是有一点要注意哈!深度侦听需要遍历被侦听对象中的所有嵌套的 属性,当用于大型数据结构时,开销很大。因此请只在必要时才使用它,并且要留意性能。

监听多个参数执行各自逻辑

本来不打算说了,但是逼逼赖赖这么久了,稍微简单提一下吧。

比如说我们需要监听多个参数,假设两个哈,然后每个参数监听到之后,执行的逻辑是不一样的,我们可以创建多个侦听器来分别监听,不写全部代码了,只写关键代码了哈。

      // 第一个
      watch(num, (newVal, oldVal) => {
        console.log(newVal, oldVal)
      })

      // 第二个
      watch(()=> boy.age, (newVal, oldVal) => {
        console.log(newVal, oldVal)
      })

监听多个参数执行相同逻辑

这个的意思就是无论是 num 改变还是 boy.age 改变,我执行的代码都是一样的。看下面案例:

<template>
  <div>
    <h1>watch 侦听器</h1>
    <el-input v-model="num.name" />
    <el-input v-model="num.age" />
    <br>
    <br>
    <el-button type="primary" @click="num.todo.time ++">num.todo.time + 1</el-button>
  </div>
</template>
<script>
  import { watch, ref, reactive, computed, } from 'vue'
  export default {
    setup() {
      const num = reactive({
        name: '我是𝒆𝒅.',
        age: 10,
        todo: {
          name: '弹吉他',
          time: 1
        }
      })

      watch([() => num.name, () => num.age], (newVal, oldVal) => {
        console.log(newVal, oldVal)
      })
      return { num }
    }
  }
</script>
<style scoped>
  .el-input {
    width: 100px;
  }
</style>

保存刷新页面,修改 name 和 age 的值。

在这里插入图片描述 上面我们把数据源以数组的方式传入,返回的回调参数,新值和旧值都是以数组的方式返回,新值旧值的数组内顺序就是我们数据源传入的顺序,都能看出来哈。

如果你不想让他返回数组你可以这样改一下,其实都差不多,了解一下,根据实际情况选择性使用就行。

<template>
  <div>
    <h1>watch 侦听器</h1>
    <el-input v-model="num.name" />
    <el-input v-model="num.age" />
    <br>
    <br>
    <el-button type="primary" @click="num.todo.time ++">num.todo.time + 1</el-button>
  </div>
</template>
<script>
  import { watch, ref, reactive, computed, } from 'vue'
  export default {
    setup() {
      const num = reactive({
        name: '我是𝒆𝒅.',
        age: 10,
        todo: {
          name: '弹吉他',
          time: 1
        }
      })

      watch([() => num.name, () => num.age], ([newName, newAge], [oldName, oldAge]) => {
        console.log(newName, newAge, oldName, oldAge)
      })
      return { num }
    }
  }
</script>
<style scoped>
  .el-input {
    width: 100px;
  }
</style>

保存刷新查看一下效果。

在这里插入图片描述

好了,今天侦听器 watch 的内容主要就是这些,晚安啦宝子们,我们明天见!