工作中在watch上踩的坑

184 阅读1分钟

起因

今天在工作中遇到一个问题,需要将一些数据整理成以下结构

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,就用原来的双向绑定,但是要注意它不能劫持部分变化的缺陷