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!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM