記錄下最近發現的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!