vue父组件data改变触发子组件prop值变化

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

 

posted on 2020-10-20 10:23  rainbowLover  阅读(3661)  评论(0编辑  收藏  举报