你真的理解watch侦听器吗?

156 阅读3分钟

1. 概念

watch 是 Vue 中一个非常重要的功能,用于观察和响应 Vue 实例上的数据变化。当需要在数据变化时执行异步或开销较大的操作时,watch 是最有用的工具。接下来就来一起看看watch

2. 作用

watch侦听器允许开发者监视数据的变化,从而针对数据的变化做特定的操作,主要表现形式为:

  1. 以对象书写,键是需要观察的表达式,值是对应回调函数
  2. 值也可以是方法名,或者包含选项的对象

3. 单一数据监听

监听单一的除对象、数组以外的数据,下面来看一个例子:

new Vue({
  data: {
    message: 'Hello Vue!'
  },
  watch: {
    message(newVal, oldVal) {
      console.log(`新值: ${newVal}, 旧值: ${oldVal}`);
    }
  }
})

以上是 watch 简写的一种形式,使用数据的名称作为监听函数的名称,参数主要包含新值和旧值,新值表示,当监听的数据发生改变,改变之后的值,那么旧值就很好理解了

接下来就是监听对象

4. 监听对象中的某个属性

new Vue({
  data: {
    user: {
      name: 'John',
      age: 30
    }
  },
  watch: {
    'user.name'(newVal, oldVal) {
      console.log(`用户名从 ${oldVal} 变为 ${newVal}`);
    }
  }
})

同样的我们需要使用对象.属性名 来作为函数名称,参数主要包含新值和旧值,这里就不作过多的介绍

5. 深度监听

默认情况下,watch 只会监听属性的直接变化,是不会去监听嵌套属性的变化的,想要监听对象内部的变化,就需要使用深度监听:

watch: {
  user: {
    handler(newVal,oldVal) {
      console.log('user 对象发生了变化');
    },
    deep: true  // 深度监听
  }
}

那么这就使用到了第二种表现形式,需要监听的对象作为键值,以对象作为值进行配置,那么这里就有一个问题,大家会发现深度监听获取到的新值和旧值是一样的,可能就会认为深度监听是获取不到旧值的,那么对于这一点我在这里展开说一下:

我们先来看一个例子:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="../utils/vue.js"></script>
</head>
<body>
<div id="app">
    <button @click="changeAge">年龄加1</button>
</div>
<script>
    new Vue({
        el: '#app',
        data: {
          // 被监听的对象
            someObject:{
                name: 'Vue',
                age:19,
                hobby:'playGames'
            }
        },
        methods: {
          // 改变age,触发监听器
            changeAge(){
                this.someObject.age++
            }
        },
        watch:{
            someObject:{
                handler(newVal,oldVal){
                    console.log(newVal)
                    console.log(oldVal)
                    console.log(newVal===oldVal);
                },
                deep:true
            }
        }
    })
</script>
</body>
</html>

接下来我们可以看到一个结果:

我们发现新值和旧值是一样的,进行比较之后也是一样的,那么究竟可不可以获取到旧值呢,答案是可以的,那么我们在说获取旧值方法前需要先来说明一下为什么会出现上边的这种情况。

其实在深度监听中,新值和旧值将会指向同一个对象的引用,这一点也是 Vue 为了性能考虑,不会对深度监听的对象做深拷贝导致的现象

5.1. 解决这一现象的方案

想要解决上边无法在深度监听中获取到旧值的问题我们可以采用手动深拷贝的方式:

watch: {
    someObject: {
        handler(newVal) {
            const oldVal = structuredClone(this._prevObject);
            console.log('真正的旧值:', oldVal);
            this._prevObject = structuredClone(newVal);
            console.log('新值:'newVal);
        },
        deep: true,
        immediate: true
    }
}

6. 可以获取到旧值的方案

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="../utils/vue.js"></script>
</head>
<body>
<div id="app">
    <button @click="changeAge">年龄加1</button>
</div>
<script>
    new Vue({
        el: '#app',
        data: {
            someObject:{
                name: 'Vue',
                age:19,
                hobby:'playGames'
            }
        },
        methods: {
            changeAge(){
                this.someObject.age++
            }
        },
        watch: {
            someObject: {
                handler(newVal) {
                    const oldVal = structuredClone(this._prevObject);
                    console.log('真正的旧值:', oldVal);

                    this._prevObject = structuredClone(newVal);
                    console.log(newVal);
                },
                deep: true,
                immediate: true
            }
        }
    })
</script>
</body>
</html>

6.1. 三个选项

hendler:参数为新值和旧值,在这里可以监听到某个属性的新旧值;

deep:是否开启深度监听

immediate:将会在监听开始之后立即执行