vue組件引用傳值的最佳實踐

下述組件傳值指引用類型(數組或對象)傳值。

準備:單向數據流

所有的 prop 都使得其父子 prop 之間形成了一個單向下行綁定:父級 prop 的更新會向下流動到子組件中,但是反過來則不行。這樣會防止從子組件意外變更父級組件的狀態,從而導致你的應用的數據流向難以理解。

額外的,每次父級組件發生變更時,子組件中所有的 prop 都將會刷新爲最新的值。這意味着你應該在一個子組件內部改變 prop。如果你這樣做了,Vue 會在瀏覽器的控制檯中發出警告。

  • 這個 prop 用來傳遞一個初始值;這個子組件接下來希望將其作爲一個本地的 prop 數據來使用。
    定義一個本地的 data property 並將這個 prop 用作其初始值

    props: ['initialCounter'],
    data: function () {
      return {
        counter: this.initialCounter
      }
    }
    
  • 這個 prop 以一種原始的值傳入且需要進行轉換。
    使用這個 prop 的值來定義一個計算屬性

    props: ['size'],
    computed: {
      normalizedSize: function () {
        return this.size.trim().toLowerCase()
      }
    }
    

注意在 JavaScript 中對象和數組是通過引用傳入的,所以對於一個數組或對象類型的 prop 來說,在子組件中改變變更這個對象或數組本身將會影響到父組件的狀態。

問題

父子組件間,通過引用類型傳值,爲什麼控制檯不會告警(基本類型會告警)?Object.assign() 或者 JSON.parse(JSON.stringify()) 是在子組件中傳引用值的標準處理方法嗎?

問題1

父組件 App.vue

<template>
  <div id="app">
    <child :initialValue="valueEmit">
    </child>
  </div>  
</template>  
<script>
import childEmit from './components/child.vue'
export default {
	data () {
    return {
      valueEmit: {dog: 1, cat: 2}
    }
  },
  components: {
    child
  }
}        
</script>        

子組件 components/child.vue

<template>
  <div class="child-container">
    <p> 
      <label for="cat">貓🐱:</label>
      <input id="cat" type="text" v-model="value.cat" />
    </p>
    <p>
      <label for="dog">狗🐶:</label>
      <input id="dog" type="text" v-model="value.dog" />
    </p>
  </div>
</template>
<script>
export default {
  name: 'child',
  props: {
    initialValue: Object
  },
  data () {
    return {
      value = Object.assing({}, this.initialValue)
    }
  }
}
</script>

子組件內的修改,父組件也會直接變更,且不告警!

問題2

修改子組件,子組件 components/child.vue

export default {
  name: 'child',
  props: {
    initialValue: Object
  },
  data () {
    return {
      valueEmit: Object.assign({}, this.initialValue)
    }
  }
}

切斷了引用,但是父組件變化不會觸發子組件響應!(computed or watch 可以實現)

問題3

父組件 App.vue

<template>
  <div id="app">
    <child
      :initialValue="valueEmit"
      @update-value-by-child="updateParentValue">
    </child>
  </div>  
</template>  
<script>
import childEmit from './components/child.vue'
export default {
	data () {
    return {
      valueEmit: {dog: 1, cat: 2}
    }
  },
  components: {
    child
  },
  methods: {
    updateParentValue(newValue) {
      this.valueEmit = newValue
    }
  }
}        
</script>        

子組件 components/child.vue

<template>
  <div class="child-container">
    <p> 
      <label for="cat">貓🐱:</label>
      <input id="cat" type="text" v-model="valueEmit.cat"
        @input="$emit('update-value-by-child', valueEmit)" />
    </p>
    <p>
      <label for="dog">狗🐶:</label>
      <input id="dog" type="text" v-model="valueEmit.dog" />
    </p>
  </div>
</template>
<script>
export default {
  name: 'child',
  props: {
    initialValue: Object
  },
  data() {
    return {
      valueEmit: Object.assign({}, this.initialValue)
    }
  }
}
</script>

現象:

  1. 首先對“dog”進行修改,父組件的 initialValue 並未發生改變
  2. 對“cat”進行修改,父組件的 initialValue 發生變化(dog、cat都被修改了)
  3. 此時,在對“dog”修改,父組件的 initialValue 發生變化!

總結

  1. 純展示
    直接使用父組件屬性,不會有副作用!

    <template>
    	<div>
        {{parentObj.value}}
      </div>
    </template>
    <script>
    	export default {
        name: 'child',
        props: {
          parentObj: Object
        }
      }
    </script>
    
  2. 只子組件內部修改,父組件不會修改(即,父組件只做初始化)
    子組件 data 中聲明新的數據,通過 Object.assign() 或者 JSON.parse(JSON.stringify()) 切斷引用即可。

    <template>
    	<div>
        <input type="text" v-model="childObj.value">
      </div>
    </template>
    <script>
    	export default {
        name: 'child',
        props: {
          parentObj: Object
        },
        data () {
          return {
            childObj: Object.assign({}, this.parentObj)
          }
        }
      }
    </script>
    
  3. 父子組件都會修改
    通過 computed 或者 watch 進行處理

    <template>
    	<div>
        <input type="text" v-model="childObj.value">
      </div>
    </template>
    <script>
    	export default {
        name: 'child',
        props: {
          parentObj: Object
        },
        computed: {
          childObj () {
            return Object.assign({}, this.parentObj)
          }
        }
      }
    </script>
    

    或者 watch 方式

    export default {
      name: 'child',
      props: {
        parentObj: Object
      },
      data () {
        return {
          childObj: {}
        }
      },
      watch: {
        parentObj: {
          handler (val, oldVal) {
            if (val) {
              this.childObj = Object.assign({}, this.parentObj)
            }
          },
          deep: true,
          immediate: true
        }
      }
    }
    

    關於 watch 和 computed 區別: vue computed正確使用方式

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章