# Vue3教程 - 7 侦听器watch

和 Vue2 不同,Vue2 中是选项式API,Vue3 中是组合式API。

在 Vue 中,watch 函数是一个非常强大的工具,它允许我们监视数据的变化,并在数据发生变化时执行特定的操作。与 Vue2 中的 watch 相比,Vue3 的 watch API 提供了更灵活和强大的功能,包括支持侦听多个数据源、深度侦听以及侦听 getter 函数等。

Vue3 中的 watch 可以监视多种类型的数据,主要有:

  • ref定义的数据

  • reactive定义的数据

  • 函数的返回值(getter函数)

  • 包含上述内容的数组

下面分别介绍一下几种常用的使用场景。

# 7.1 监视ref定义的基本类型数据

下面定义了基本数据类型的 sum,然后点击按钮,将 sum 加 1,然后使用 watch 监听 sum 的值变化。

<template>
  结果为:{{ sum }} <br/>

  <button @click="plusOne">加1</button>
</template>

<!-- setup -->
<script lang="ts" setup>
import { ref, watch } from 'vue'  // 1.引入 watch

let sum = ref(0)

function plusOne() {
  sum.value++
}

// 监视sum的值的变化
watch(sum, (newValue, oldValue)=> {
  console.log("newValue:", newValue, "oldValue:", oldValue)
})

</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

首先引入 watch 函数,watch 函数第一个参数是监听的变量,第二个参数是一个回调函数,参数是新值旧值

当点击按钮的时候,sum的值累加,控制台打印了 watch 的回调函数中的执行结果。


如何取消监视呢?

watch() 返回的是一个停止函数,调用返回的函数就可以取消监视了。

// 监视sum的值的变化
const stopWatch = watch(sum, (newValue, oldValue)=> {
  console.log("newValue:", newValue, "oldValue:", oldValue)

  // newValue大于等于5的时候,取消监视
  if (newValue >= 5) {
    stopWatch()
  }
})
1
2
3
4
5
6
7
8
9

# 7.2 监视ref定义的对象类型数据

首先使用 ref 定义一个对象类型的响应式数据,然后使用按钮触发修改这个对象的属性和修改整个对象:

<template>
  <div>{{ person.name }}</div>

  <button @click="changeName">修改name</button>
  <button @click="changePerson">修改person</button>
</template>

<!-- setup -->
<script lang="ts" setup>
import { ref, watch } from 'vue'  // 1.引入 watch

let person = ref({"name": "doubi", "age": 13})

// 修改name
function changeName() {
  person.value.name = "niubi"
}

// 修改person
function changePerson() {
  person.value = {"name": "niubi", "age": 15}
}

// 监视person的值的变化
watch(person, (newValue, oldValue)=> {
  console.log("newValue:", newValue, "oldValue:", oldValue)
})

</script>
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

运行的结果发现,修改对象的时候,可以触发回调, newValue 是新的对象, oldValue 是旧的对象。而当修改对象的属性的时候,是无法触发 watch 回调的。

默认情况下,watch 监视的是对象的地址是否发生变化,所以如果想监听属性的值是否发生变化,需要手动开启深度监视

需要给 watch 第三个参数,第三个参数是一个配置对象,可以对 watch 进行一些配置。

此时就可以监听属性值的变化了:

// 监视person的值的变化,并开启深度监视
watch(person, (newValue, oldValue)=> {
  console.log("newValue:", newValue, "oldValue:", oldValue)
}, {"deep":true})
1
2
3
4

但是需要注意,此时监视数据的变化,你会发现 newValueoldValue 是一样的,因为此时对象没有变化,他们指向的是同一个 person 对象。

也就是说:

  • 如果修改的是整个对象,newValue是新值,oldValue是旧值,因为不是同一个对象了;

  • 如果修改的是对象中的属性,newValue和oldValue都是新值,因为它们是同一个对象;


另外,第三个参数不止 deep 一个参数,还有一些其他的配置,例如 immediate 等等:

// 监视person的值的变化
watch(person, (newValue, oldValue)=> {
  console.log("newValue:", newValue, "oldValue:", oldValue)
}, {deep:true, immediate:true})
1
2
3
4

immediate 表示会立即触发一次,也就是进入页面就会触发一次监视的回调函数,此时 newValue 是初始化值,oldValueundefined

# 7.3 监视reactive定义的对象类型数据

reactive 只能定义对象类型的数据,在使用 watch 监视 reactive 定义的对象类型的数据的时候,默认是开启深度监视的,且无法关闭深度监视

举个栗子:

<template>
  <div>{{ person.name }}</div>

  <button @click="changeName">修改name</button>
  <button @click="changePerson">修改person</button>
</template>

<!-- setup -->
<script lang="ts" setup>
import { reactive, watch } from 'vue'  // 1.引入 watch

// 创建响应式数据
let person = reactive({"name": "doubi", "age": 13})

// 修改name
function changeName() {
  person.name = "niubi"
}

// 修改person
function changePerson() {
  // 前面讲了,不能直接修改person,需要使用Object.assign进行赋值
  Object.assign(person, {"name": "niubi", "age": 15})
}

// 监视person的值的变化
watch(person, (newValue, oldValue)=> {
  console.log("newValue:", newValue, "oldValue:", oldValue)
})

</script>
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

在前面的章节讲了,不能直接将新的对象赋值给 person,需要使用 Object.assign 将一个对象的各个属性的值赋值给 person 对象的属性。

然后使用 watch 监听 person 数据的变化,因为对象的地址没有变化,所以 newValue 和 oldValue 是同一个对象,所以他们的值是一样的。

运行结果:

# 7.4 监视对象的属性

监视 refreactive 定义的对象类型的数据的属性

监视的时候,属性分为两种情况,一种是基本数据类型的属性,一种是对象类型的属性。

为了演示,定义 person 对象,添加基本类型的属性和对象类型的属性。

如下:

<template>
  <div>{{ person.name }}</div>
  <div>{{ person.girlfriends.first }},{{ person.girlfriends.second }}</div>

  <button @click="changeName">修改基本类型属性</button>
  <button @click="changeFirstGirlFriend">修改对象属性</button>
  <button @click="changeGirlFriends">改变整个对象属性</button>
</template>

<!-- setup -->
<script lang="ts" setup>
import { reactive, watch } from 'vue'  // 1.引入 watch

// 定义响应式数据
let person = reactive({
  name: "doubi", 
  girlfriends: {  // 定义女朋友们
    first: "赵今麦",
    second: "李沁"
  }
})

// 修改name
function changeName() {
  person.name = "niubi"
}

// 修改第一个女朋友
function changeFirstGirlFriend() {
  person.girlfriends.first = "高圆圆"
}

// 修改所有的女朋友
function changeGirlFriends() {
  person.girlfriends = {first: "战鹰", second: "刘亦菲"}
}

</script>
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

下面来监听对象的属性。

# 1 监听基本数据类型的属性

watch 不能直接监听 person.name ,在前面说的 watch 能监听 函数的返回值(getter函数),所以这里需要将 person.name 通过一个函数返回。

// 监视person.name值的变化
watch(() => { return person.name }, (newValue, oldValue)=> {
  console.log("newValue:", newValue, "oldValue:", oldValue)
})
1
2
3
4

箭头函数可以进一步简写:

watch(() => person.name , (newValue, oldValue)=> {
  console.log("newValue:", newValue, "oldValue:", oldValue)
})
1
2
3

运行效果:

# 2 监视对象类型的属性

监视对象类型的属性,可以直接进行监视:

// 监视person.girlfriends值的变化,直接监视
watch(person.girlfriends , (newValue, oldValue)=> {
  console.log("newValue:", newValue, "oldValue:", oldValue)
})
1
2
3
4

但是此时只能监视到对象类型属性值的变化,而无法监视到整个属性对象的替换(地址变化),也就是无法监视到 person.girlfriends = {first: "战鹰", second: "刘亦菲"},如果要监视对象变化,还是需要写成函数的形式:

// 使用函数的方式监听
watch(() => person.girlfriends , (newValue, oldValue)=> {
  console.log("newValue:", newValue, "oldValue:", oldValue)
}, {deep: true})
1
2
3
4

但是需要注意,最后需要添加 {deep: true} ,否则只能监视对象属性整体的变化,无法监视到对象属性中单个属性的变化。

总结:

  • 若属性不是对象类型,需要写成函数形式;

  • 如果属性是对象类型,可直接监视,但是推荐写成函数形式,如果想进行深度监视,可以使用 {deep: true}

# 7.5 监视多个数据

如果要监视多个数据,只需要将要监视的数据放在一个数组中就可以了,当然要监视的数据是上面支持被监视的类型。

在上面监视对象的属性的例子上进行修改,监视 person.nameperson.girlfriends.second :

// 将要监视的数据放到数组中即可
watch([() => person.name, () => person.girlfriends.second], (newValue, oldValue)=> {
  console.log("newValue:", newValue, "oldValue:", oldValue)
}, {deep: true})
1
2
3
4

回调函数的 newValueoldValue 是整个数组。