使用watch监听为什么有时不生效? 这篇文章或许可以给你答案,看完还不懂,请来找我。
举个栗子 父组件有个element对象, 每次添加商品时需要往element.data里面去添加一条数据, 以此来更新父组件以及子组件的内容,通过$emit的形式更新父组件的element。 代码如下:
<div class="item-box" v-for="(product, paramIndex) in element.data" :key='paramIndex'> <div class="wj-item wj-border"> <v-image :src="product.thumbUrl"></v-image> <div class="wj-name">{{product.skuName}}</div> <div class="wj-price">{{product.retailPrice | currency}}</div> </div> <div class="wj-right-bar lCenter right"> <div class="wj-arrow-icon wj-top-red" v-show="paramIndex != 0" @click="removeTop(paramIndex)"></div> <div class="wj-arrow-icon wj-bottom-red" v-show="paramIndex != element.data.length - 1" @click="removeBottom(paramIndex)"></div> <div class="wj-arrow-icon wj-delete" @click="deleteOptions(paramIndex)"></div> </div> </div> props: { element: { type: Object, required: true } }, methods:{ addProduct() { this.$emit('show.search', 'multiple', this.element.data) }, }然而element.name等其他属性可以显示, 但element.data里面的数据却没有更新。 原因是 由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。 对于无法监听数组的改动,官方提供了两种方式:
// (1).Vue.set Vue.set(vm.items, indexOfItem, newValue) //(2). Array.prototype.splice vm.items.splice(indexOfItem, 1, newValue)但对于上述情况,一个个去赋值,并不是一个好的方式,那么可以使用watch进行监听。
watch: { 'element.data': function(val) { console.log(this.element.data);//可以拿到数据 }, },这样就可以监听到element.data数组里面的改动了。
tips: (1) watch监听优化: 监听某个对象时,对象的任何属性改变都会触发变动, 这样比较耗性能, 如果明确知道只需监听某一属性,可以使用字符串的形式监听,如’element.data’。 (2) watch有一个特点,就是当值第一次绑定的时候,不会执行监听函数,只有值发生改变才会执行。如果我们需要在最初绑定值的时候也执行函数,可添加immediate属性。 (3) 普通watch方法不能监听对象内部属性的变化,可以添加deep属性深度监听。
那么watch内部是如何实现监听的呢?一起来看看watch的内部实现。
要知道watch的工作原理, 需要了解三个地方: a.监听数据改变时,watch 如何工作 b.设置 immediate 时,watch 如何工作 c.设置了 deep 时,watch 如何工作
Vue会把数据设置响应式,即设置他的 get 和 set 当数据被读取,get被触发,然后收集到读取他的东西,保存到依赖收集器 当数据被改变,set被触发,然后通知曾经读取他的东西进行更新。 watch 在一开始初始化的时候,会 读取 一遍 监听的数据的值,于是,此时 那个数据就收集到 watch 的 watcher 了 然后 你给 watch 设置的 handler ,watch 会放入 watcher 的更新函数中 当 数据改变时,通知 watch 的 watcher 进行更新,于是 你设置的 handler 就被调用了.
设置了 immediate 时,就不需要在数据改变的时候才会触发。 而是在 初始化 watch 时,在读取了 监听的数据的值 之后,便立即调用一遍你设置的监听回调,然后传入刚读取的值.
watch 有一个 deep 选项,是用来深度监听的,什么是深度监听呢?就是当你监听的属性的值是一个对象的时候,如果你没有设置深度监听,当对象内部变化时,你监听的回调是不会被触发的.
function _traverse (val: any, seen: SimpleSet) { let i, keys const isA = Array.isArray(val) //是否为数组 if ((!isA && !isObject(val)) // 如果不是array和object || Object.isFrozen(val) // 或者是已经冻结对象 || val instanceof VNode) // 或者是VNode实例 { return } if (val.__ob__) {//只有object和array才有__ob__属性 const depId = val.__ob__.dep.id if (seen.has(depId)) { // 已经有收集过 return } seen.add(depId) //没有被收集过 } if (isA) { //如果是数组, 递归进行收集 i = val.length while (i--) _traverse(val[i], seen) } else { keys = Object.keys(val) i = keys.length while (i--) _traverse(val[keys[i]], seen) } }从vue源码可以看到, 当存在deep属性时,会执行traverse方法。 简单来讲,就是递归收集对象或数组的子属性值。
tips: 需要注意的是,不应该使用箭头函数来定义 watcher 函数 (例如 searchQuery: newValue => this.updateAutocomplete(newValue))。理由是箭头函数绑定了父级作用域的上下文,所以 this 将不会按照期望指向 Vue 实例,this.updateAutocomplete 将是 undefined。(官方文档:watch)
举个栗子
设置运费模板时, 因为接口处理是按分为单位的, 而用户输入的是元, 中间需要做转换,由于使用了watch监听, 在处理传参时页面数据竟然也被修改了,代码如下:
//formItem数据结构如下: forItem:{ freightList:[{ initFreight: 10, addFreight: 10, provinceIdList:[], }] ... } //watch监听: forItem: { handler(newVal, oldVal){ console.log(this.formItem) }, deep: true, immediate: true },在提交时设置initFreight*100之后, 页面上显示的数据也变成了1000。我试图把formItem赋值给newForm, 可改变newForm中的initFreight时,页面依然会改变。 这是为什么呢? 原因是把formItem赋值给newForm后, 它们指向的是同一个地址,即值引用。
newForm = JSON.parse(JSON.stringify(this.forItem));只需要使用JSON转义就可以解决问题了。v-model出现这个问题也是同样的解决方法。
推荐文档: 对vue响应式数据更新的误解 watch源码 深入理解Vue的手表实现原理及其实现方式 搞懂computed和watch原理,减少使用场景思考时间 Watch - 源码解析