记录下最近发现的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!