Vue 属性侦听(3)

274 阅读3分钟

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

1. 侦听器的其它写法

前面我们编写侦听器时,键是要侦听的响应式属性,值是一个函数或者一个对象。其实,值除了可以是函数或对象,还可以是下面两种类型:

  • (方法名)字符串;
  • (回调)数组;

1.1 字符串方法名

用法如下:

// 字符串方法名
b: 'someMethod',

上篇文章中的例子举例,可以这样写(<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: 'infoChangeHandler'
    },
    methods: {
      changeInfo() {
        this.info = { name: 'Filan' };
      },
      changeInfoName() {
        this.info.name = 'Filan';
      },
      infoChangeHandler(newInfo, oldInfo) {
        console.log('newInfo:', newInfo, 'oldInfo', oldInfo);
      }
    },
    template: '#my-app'
  };

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

页面效果和上篇文章中的第一段代码的页面效果是一样的,即默认情况下侦听器只能侦听到数据本身的变化,而不能侦听到数据内部的变化。所以侦听器字符串方法名的写法:

watch: {
  // 字符串方法名
  info: 'infoChangeHandler'
},
methods: {
  infoChangeHandler(newInfo, oldInfo) {
    console.log('newInfo:', newInfo, 'oldInfo', oldInfo);
  }
}

等价于:

watch: {
  info(newInfo, oldInfo) {
    console.log('newInfo:', newInfo, 'oldInfo', oldInfo);
  }
}

字符串方法名这种写法一般用的较少。

1.2 回调数组

用法如下:

// 你可以传入回调数组,它们会被逐一调用
f: [
  'handle1',
  function handle2(val, oldVal) {
    console.log('handle2 triggered')
  },
  {
    handler: function handle3(val, oldVal) {
      console.log('handle3 triggered')
    }
    /* ... */
  }
]

举例如下(<body> 元素中的代码):

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

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

<script src="./js/vue.js"></script>
<script>
  const App = {
    data() {
      return {
        info: { name: 'zhj', age: 20 }
      }
    },
    watch: {
      info: [
        'handle1',
        function handle2(val, oldVal) {
          console.log('handle2 triggered');
        },
        {
          handler: function handle3(val, oldVal) {
            console.log('handle3 triggered');
          },
          /* ... */
        }
      ]
    },
    methods: {
      changeInfo() {
        this.info = { name: 'Filan' };
      },
      handle1() {
        console.log('handle1 triggered');
      }
    },
    template: '#my-app'
  };

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

页面效果如下:

侦听器的其它写法-回调数组-16310114558701.gif

1.3 侦听对象的某个属性

这种写法在 Vue3 文档中没有提到,但Vue2 文档中有提到

watch: {
  // watch vm.e.f's value: {g: 5}
  'e.f': function (val, oldVal) { /* ... */ }
}

前面我们讲到侦听器的配置选项 deep 设置为 true 后,就可以侦听到对象中所有属性的变化了,而如果我们只想侦听对象中某个属性的变化,比如 4.2 节的例子中 info 对象中 name 属性的变化,就可以这样写:

watch: {
  // 注意写成字符串格式
  'info.name': function(newName, oldName) {
    console.log(newName, oldName);
  }
}

对于 'info.name'Vue 内部会帮我们解析出要侦听的是 info 对象的 name 属性。

不过,直接侦听数组中对象元素的某个属性是侦听不到的:

data() {
  return {
    friends: [{ name: 'zhj' }, { name: 'Filan' }]
  }
},
watch: {
  // 无效侦听,直接侦听数组元素对象的某个属性是侦听不到的
  'friends[0].name': function(newVal, oldVal) {
    console.log(newVal, oldVal);
  }
}

如果真想侦听数组中对象元素的某个属性,有两种办法:

  1. 侦听数组本身,同时设置 deep 选项为 true

    watch: {
      'friends': {
        handler(newVal, oldVal) {
          // ...
        },
        deep: true
      }
    }
    
  2. (推荐)开发中通常情况下我们会对数组进行遍历展示,而数组的每个元素都可以封装成一个组件,该组件中可以通过在 props 选项中定义一个属性拿到数组的元素对象,所以我们就可以直接在该组件中侦听此对象的某个属性。实现流程如下:

image-20210913183944650.png

1.4 实例方法 $watch

我们还可以在生命周期钩子 created() 函数(当组件创建完成后,会自动执行这个函数中的代码,这个函数和 datacomputedwatch 等选项是处于同级的,具体后续会讲到)中,通过 this.$watch() 这个 API 来进行侦听:

  • 第一个参数是要侦听的源;
  • 第二个参数是侦听的回调函数;
  • 第三个参数是可选的,是一个对象,里面有三个属性可以设置:deepimmediateflush

$watch 会返回一个函数,后续如果我们想取消侦听,就可以调用这个函数(而写在 watch 选项中的侦听器则不能取消)。

示例:

data() {
  return {
    info: { name: 'zhj', age: 20 },
    message: '你好'
  }
},
created() {
  // console.log(this.message); // 你好
  // 这里的回调函数可以写成箭头函数,因为这里箭头函数中的 this 就是外层作用域 created 函数中的 this
  const unwatch = this.$watch('info', (newInfo, oldInfo) => {
    console.log(newInfo, oldInfo);
    // console.log(this.message); // 你好
  }, {
    deep: true,
    immediate: true
  });

  // 取消侦听
  // unwatch();
}

当然,在开发中,我们通常使用的侦听器是比较简单的,很少遇到非常复杂的情况。