记录下最近发现的vue的一个小bug,或者说vue的一个小坑:
项目中父组件引用子组件,子组件对传递过来的prop之value设置了监听, 父组件更改和prop之value无关的属性值,会触发子组件的watch;说不清楚还是看代码吧:
// 父组件
<template>
<div class="home">
<HelloWorld :value='[1]'/>
<div>res:{{propsData}}</div>
<button @click="setPropData">setPropData</button>
</div>
</template>
<script>
import HelloWorld from '@/components/HelloWorld.vue'
export default {
name: 'Home',
components: {
HelloWorld
},
data() {
return {
propsData:[123],
value:[1]
}
},
methods: {
setPropData(){
this.propsData=[12312333]
}
},
}
</script>
// 子组件
<template>
<div class="hello">
<p>{{value}}</p>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
value:{
type: Array,
default () {
return []
}
},
},
watch: {
value: {
deep: true,
handler (val, oldVal) {
console.log('val :>> ', val)
console.log('oldVal :>> ', oldVal)
console.log('val === oldVal :>> ', val == oldVal)
}
}
},
}
</script>
其中子组件监听了传过来的value, 父组件传过来了一个数组, 这时候父组件响应点击事件,设置其他的值会触发子组件的watch,这肯定不是咱们预想的效果,因为监听value的值要去更新子组件的状态,总不能父组件任何一个属性变化我都更新下子组件的状态,咋办呢?
尝试: 子组件watch中判断新值和旧值是否相同,如果不相同就做更新操作,当然==是不能满足要求的,看我比较的方法吧:
isObjectValueEqual (a, b) { // 判断两个对象是否指向同一内存,指向同一内存返回true if (a === b) return true // 获取两个对象键值数组 const aProps = Object.getOwnPropertyNames(a) const bProps = Object.getOwnPropertyNames(b) // 判断两个对象键值数组长度是否一致,不一致返回false if (aProps.length !== bProps.length) return false // 遍历对象的键值 for (const prop in a) { // 判断a的键值,在b中是否存在,不存在,返回false if (Object.prototype.hasOwnProperty.call(b, prop)) { // 判断a的键值是否为对象,是则递归,不是对象直接判断键值是否相等,不相等返回false if (typeof a[prop] === 'object') { if (!isObjectValueEqual(a[prop], b[prop])) return false } else if (a[prop] !== b[prop]) { return false } } else { return false } } return true },
题外再多说一嘴:b.hasOwnProperty(prop)这样用eslint不会通过,因为如果对象b有一个属性刚好叫hasOwnProperty,而不是方法,那就报错了,所以保险起见用call实现,这也是个面试题呀!
这样貌似ok了吧,但还是有问题,例如给子组件传的是[1],子组件状态改成1对应的,用户滑动改变子组件的状态,这时候父组件重新传入[1],目的是让子组件归为,这时候通过上边的这个方法判断,值没有变,就不会继续操作,所以引入了一个新的bug, 改bug最操蛋的就是改一个小bug引出一个大bug,分析发现这个大bug还避免不了,咋办呢?
回到开始的位置,父组件传值时候换个方式:不直接传数组,传data中定义的对象,如下:

这样就不会触发了, 但还是没有解决最初的那个问题
这个问题出现的原因,翻了半天的vue源码也没有找到咋回事,---猜想可能是对象地址引用的问题,直接传[1]每次更改视图时候,传给子组件的是一个新的[1],换成传递data中数组对象的方式后,每次都是传递的对象的引用地址而不是一个新数组对象,所以不会触发watch.如果直接传递{a:'1'}这样的话应该是一样的效果.
over!