Vue 属性侦听(2)

142 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

1. 侦听器的配置选项

  • 我们来看一个例子:
    • 假设 data 选项返回的对象中有一个 info 属性,其对应的值也是一个对象:{ name: 'zhj', age: 20 }
    • 同时页面上有两个按钮,点击第一个按钮时,直接修改 info 本身的值,这时我们使用侦听器是可以侦听到 info 值的变化的;
    • 点击第二个按钮时,修改 info.name 的值,这时我们使用侦听器来侦听 info,可以侦听到吗?答案是不可以
  • 这是因为默认情况下,侦听器只是在侦听 info 的引用变化,而对 info 内部属性的变化是不会做出响应的:
    • 这个时候我们就可以使用侦听器的 deep 选项进行更深层的侦听;
    • 注意前面说过,侦听器的用法的类型中,侦听的属性对应的也可以是一个 Object,所以在这个 Object 中就可以配置 deep 选项了;
  • 此外,如果希望侦听器一开始就立即执行一次,就可以使用另外一个配置选项:immediate
    • 这样无论后面数据是否有变化,侦听器函数都会先执行一次;

<body> 元素中代码如下:

<div id="app"></div>

<template id="my-app">
  <h2>{{ info.name }}</h2>
  <button @click="changeInfo">修改 info</button>
  <button @click="changeInfoName">修改 info.name</button>
</template>

<script src="./js/vue.js"></script>
<script>
  const App = {
    data() {
      return {
        info: { name: 'zhj', age: 20 }
      }
    },
    watch: {
      // 默认情况下侦听器只能侦听到数据本身的变化,而不能侦听到数据内部的变化。
      info(newInfo, oldInfo) {
        console.log('newInfo:', newInfo, 'oldInfo:', oldInfo);
      }
    },
    methods: {
      changeInfo() {
        this.info = { name: 'Filan' };
      },
      changeInfoName() {
        this.info.name = 'Filan';
      }
    },
    template: '#my-app'
  };

  Vue.createApp(App).mount('#app');
</script>

页面效果:

侦听器的配置选项引出.gif

可以看到,不管是改变 info 还是 info.name,界面都会进行响应(更新),但侦听器默认情况下只能侦听到 info 本身的变化,而不能侦听到 info.name 的变化(即不能侦听到 info 内部的变化)。

你可能会问,侦听器侦听到的新旧对象的值怎么不是简单的对象,而是 proxy 呢?这是因为我们前面有说过,data 返回的对象最终是会交给响应式系统去处理的(通过 Proxy 实现数据劫持),而响应式系统处理之后,data 返回的对象包括其中的属性对应的对象,都会变成 Proxy 对象,具体原理我们后面再说。

那如果我们也想对 info 对象内部的属性进行侦听,该怎么做呢?做法很多,我们这里先用侦听器的 deep 选项来发现对象内部值的变化:

首先我们要知道,在 watch 选项中下面两种写法是等价的:

// Function 写法
info(newInfo, oldInfo) {
  console.log('newInfo:', newInfo, 'oldInfo:', oldInfo);
}

等价于

// Object 写法
info: {
  handler(newInfo, oldInfo) {
    console.log('newInfo:', newInfo, 'oldInfo:', oldInfo);
  }
}

为了配置 deep 选项实现深度侦听,我们采用 Object 写法,修改前面代码中 watch 选项中的内容如下:

info: {
  handler(newInfo, oldInfo) {
    console.log('newInfo:', newInfo, 'oldInfo:', oldInfo);
  },
  deep: true // 深度侦听
}

再来查看页面效果:

侦听器的配置选项-deep.gif

可以看到,开启深度侦听后,就能侦听到对象内部值的变化了。

而且,还可以侦听更深度的数据:

修改 <body> 元素中代码如下:

<div id="app"></div>

<template id="my-app">
  <!-- <h2>{{ info.name }}</h2> -->
  <h2>{{ info.CBA.name }}</h2>
  <!-- <button @click="changeInfo">修改 info</button>
	<button @click="changeInfoName">修改 info.name</button> -->
  <button @click="changeInfoCBAName">修改 info.CBA.name</button>
</template>

<script src="./js/vue.js"></script>
<script>
  const App = {
    data() {
      return {
        info: { name: 'zhj', age: 20, CBA: { name: 'yjl' } }
      }
    },
    watch: {
      // 默认情况下侦听器只能侦听到数据本身的变化,而不能侦听到数据内部的变化。
      // info(newInfo, oldInfo) {
      //   console.log('newInfo:', newInfo, 'oldInfo:', oldInfo);
      // }
      info: {
        handler(newInfo, oldInfo) {
          console.log('newInfo:', newInfo, 'oldInfo:', oldInfo);
        },
        deep: true // 深度侦听
      }
    },
    methods: {
      changeInfo() {
        this.info = { name: 'Filan' };
      },
      changeInfoName() {
        this.info.name = 'Filan';
      },
      changeInfoCBAName() {
        this.info.CBA.name = '易建联';
      }
    },
    template: '#my-app'
  };

  Vue.createApp(App).mount('#app');
</script>

页面效果:

侦听器的配置选项-deep(2).gif

注意:当变更(不是替换)对象或数组并使用 deep 选项时,旧值将与新值相同,因为它们的引用指向同一个对象/数组。Vue 不会保留变更之前值的副本1。我们也可以去看下源码,源码中就是引用的赋值,而没有做深拷贝:

image-20210913123836621.png

最后,再来讲下立即执行配置选项 immediate:我们知道,在我们第一次进入页面时,侦听器是不会执行的,因为没有任何数据发生改变。但有时候可能会有这样的需求,就是在页面渲染完成之后,就要执行一次侦听器中的代码,即使数据还没有发生变化。

修改前面代码中 watch 选项中的内容如下:

info: {
  handler(newInfo, oldInfo) {
    console.log('newInfo:', newInfo, 'oldInfo:', oldInfo);
  },
  deep: true, // 深度侦听
  immediate: true // 立即执行,一定会执行一次
}

页面效果如下:

侦听器的配置选项-immediate.gif

因为是立即执行的,所以侦听的数据的旧值还没有,因此旧值是 undefined

Footnotes

  1. 参考资料:v3.cn.vuejs.org/api/instanc…