小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
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>
页面效果:
可以看到,不管是改变 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 // 深度侦听
}
再来查看页面效果:
可以看到,开启深度侦听后,就能侦听到对象内部值的变化了。
而且,还可以侦听更深度的数据:
修改 <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
选项时,旧值将与新值相同,因为它们的引用指向同一个对象/数组。Vue
不会保留变更之前值的副本1。我们也可以去看下源码,源码中就是引用的赋值,而没有做深拷贝:
最后,再来讲下立即执行配置选项 immediate
:我们知道,在我们第一次进入页面时,侦听器是不会执行的,因为没有任何数据发生改变。但有时候可能会有这样的需求,就是在页面渲染完成之后,就要执行一次侦听器中的代码,即使数据还没有发生变化。
修改前面代码中 watch
选项中的内容如下:
info: {
handler(newInfo, oldInfo) {
console.log('newInfo:', newInfo, 'oldInfo:', oldInfo);
},
deep: true, // 深度侦听
immediate: true // 立即执行,一定会执行一次
}
页面效果如下:
因为是立即执行的,所以侦听的数据的旧值还没有,因此旧值是 undefined
。