vue watch监听不到对象,探究 watch 原理

  • 2020 年 12 月 29 日
  • 筆記

最近使用vue watch时,在某些模块监听不到对象的改变,无法触发回调函数。

解决:

  使用watch监听对象时,只能监听到该对象初始化时已存在的key值。

  如下例监听user对象,在初始化时没有age属性,那在mounted中给user.age赋值后不会触发watch中的回调:

var app = new Vue({
        el: '#app',
        data: {
            user: {
                name: 'zhangsan'
            }
        },
        mounted() {
            var _this = this
            setTimeout(() => {
                _this.user.age = 10
            },2000)
        },
        watch: {
            user: {
                handler (val) {
                    console.log('user update')
                },
                deep:true
            }
        }
    })

若想监听到这个变化,需要给user初始化的age:

data: {
            user: {
                name: 'zhangsan',
                age: 1
            }
        },

问题原因及vue watch原理:

  这个问题其实是比较低级的错误,说明对vue的设计原理和理念还是理解太浅。watch的基本用法这里不再赘述,下面通过遇到的这个问题,探究一下watch对object类型进行监听的用法和原理。

  通过官方文档我们知道,当监听的目标用对象进行配置时,共有三个配置属性:

watch: {
a:{  
  handler: function (val, oldVal) { /* ... */ },
  immediate:true,
  deep: true
 }
}

 

  首先,handler是vue中定义好的属性名,在用对象配置时,回调函数只能写在handler中。如下图源码所示,在creatWatcher时,会验证传入的配置项handler是否为一个纯对象类型,如果是对象类型,则会取handler.handler作为真正的回调函数;而下面的代码则是只传递方法名或方法实体的处理逻辑。

 

  关于deep属性,大家都知道vue的绑定原理是建立在Object.defineProperty的set和get方法上的。给某个属性绑定set和get之后,只有改变该属性本身时,才会触发set和get的回调函数,而改变其内部属性时不会触发。所以在vue内部创建watcher时会有一个traverse方法,当配置deep为true时,调用traverse遍历其下每一个子属性。如下源代码:

而我们又知道vue只有在组件初始化时,会对data中数据进行set和get的绑定。这也是为什么在后续的业务逻辑中,给某个对象插入新属性时不会触发watch监听的原因。

 

  immediate属性为true时,watcher创建后会立刻执行回调,这点不需要过多的介绍。