起因
今天在工作中遇到一个问题,需要将一些数据整理成以下结构
peops: ['attach']
watch: {
enclosure: {
deep: true,
handler() {
console.log("watch")
}
}
},
computed: {
enclosure: {
get() {
return this.attach
},
set(val) {
this.$emit('update:attachChange', val)
}
}
},
methods() {
...
fun() {
...
this.enclosure = [
{
images: [{...}, {...}, {...}],
link: [{...}, {...}, {...}],
radar_link: [{...}, {...}, {...}]
...
}
]
}
}
视图是通过循环enclosure这个数组进行渲染的,当我触发删除函数时,会将匹配到的最深层级下的对象删除,奇怪的是视图没有更新,同时watch中监听的enclosure也没有触发handler里面的回调。 先排除删错了的情况,因为切换以下tab栏就会显示删除后的视图,所以删除是成功的。
思考
为什么深度监听会失效呢?在查阅了相关文章后,才新发现是自己理解的太浅显了。
查找资料
查阅了很多文章,才总算有了更深的理解。
普通的watch
平时我们会在data中声名需要的变量,假设这个变量是数组或者对象,现在我们在watch中监听它,当对象或数组的值发生变化时,watch没有被触发,这是因为数组和对象都是引用类型,引用类型指向的是地址,地址不变,就不会触发监听。所以Vue不能检测对象属性的添加和删除。
deep
这时就需要使用deep了,deep是深度监听,监听器会一层层往下遍历,给对象的所有属性都加上这个监听器,但是这样性能开销就比较大,修改obj里面的任何属性都会触发这个监听器的handler
优化
可以将要监听的属性转化为字符串的形式进行监听
watch: {
'obj.a': {
handler(new, old) {
console.log('obj.a changed');
},
immediate: true,
deep: true
}
}
结合实际情况
那我遇到的属于哪种情况呢?
都不属于!
按理说我加了deep就会给每一个属性都加上getter和setter,但是为什么没有被监听到呢?
因为,我的enclosure里面的属性是动态添加的,没有声名在data中,在页面初始化时我里面的属性没有被挂载上getter和setter。Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data
对象上存在才能让 Vue 将它转换为响应式的。
详情参考vue文档:cn.vuejs.org/v2/guide/re…
所以监听当前组件中的对象,需要在data中声名。监听props中的对象,需要在父组件的data中声名。
结论
既然已经知道原因,常用的解决办法就是通过$set
手动添加响应式 property。但是还要注意,深拷贝的时候会将被监听元素的get和set去掉!同样会失去监听效果。
Vue3中对此的优化,使用proxy
在vue双向绑定原理上,我们会用到一个对象的一个属性叫Object.defineProperty,它可以为接收的参数设置get和set函数,
结合数据劫持和发布订阅模式可以实现数据的双向绑定。
而vue3中会采用Proxy来实现数据劫持,因为我们知道Object.defineProperty 是有局限性的,他的拦截的 target 就是单纯的对象的key的值,所以当对象属性的删减,数组长度的改变,它就没法进行劫持了。Proxy是ES6的新特性,它可以拦截对象、数组几乎一切对象类型,但是没办法兼容IE,所以 Vue3.0 底层还是采用 Object.defineProperty
而 使用 Proxy 作为一个 api ,也就是说:
我们不兼容IE, 就大胆用 Proxy 双向绑定而且不会有属性删减和数组劫持不到的问题
我们要兼容IE,就用原来的双向绑定,但是要注意它不能劫持部分变化的缺陷