使用 props 將資料從父級傳遞給子級

在 Vue.js 中,每個元件例項都有自己的獨立範圍,這意味著如果父元件具有子元件 - 子元件具有其自己的隔離範圍,而父元件具有其自己的隔離範圍。

對於任何中型到大型應用程式,遵循最佳實踐約定可以防止在開發階段和維護之後出現許多令人頭疼的問題。要遵循的一個方面是避免直接從子元件引用/改變父資料。那麼我們如何從子元件中引用父資料呢?

子元件中所需的父資料應該從父元件傳遞給子元件 props

使用案例 :假設我們有一個包含兩個表 usersaddresses 的使用者資料庫,其中包含以下欄位:
users Table

名稱 電話 電子郵件
約翰麥克蘭 (1)234 5678 9012 john@dirhard.com
佔士邦 (44)777 0007 0077 bond@mi6.com

addresses

Nakatomi 塔 百老匯 紐約
Mi6 House 白金漢路 倫敦

我們希望有三個元件在我們的應用程式中的任何位置顯示相應的使用者資訊

使用者 component.js

export default{
    template:`<div class="user-component">
                <label for="name" class="form-control">Name: </label>  
                <input class="form-control input-sm" name="name" v-model="name">
                <contact-details :phone="phone" :email="email"></contact-details>
              </div>`,
    data(){
        return{
            name:'',
            phone:'',
            email:''
        }
    },
}  

接觸 details.js

import Address from './address';
export default{
    template:`<div class="contact-details-component>
                <h4>Contact Details:</h4>
                <label for="phone" class="form-control">Phone: </label>  
                <input class="form-control input-sm" name="phone" v-model="phone">
                <label for="email" class="form-control">Email: </label>  
                <input class="form-control input-sm" name="email" v-model="email"> 
        
                <h4>Address:</h4>
                <address :address-type="addressType"></address>
                //see camelCase vs kebab-case explanation below
            </div>`,
    props:['phone', 'email'],
    data:(){
        return:{
            addressType:'Office'
        }
    },
    components:{Address}  
}  

address.js

export default{
    template:`<div class="address-component">
                <h6>{{addressType}}</h6>
                <label for="block" class="form-control">Block: </label>  
                <input class="form-control input-sm" name="block" v-model="block">
                <label for="street" class="form-control">Street: </label>  
                <input class="form-control input-sm" name="street" v-model="street">
                <label for="city" class="form-control">City: </label>  
                <input class="form-control input-sm" name="city" v-model="city">
             </div>`,
    props:{
        addressType:{
            required:true,
            type:String,
            default:'Office'
        },
    data(){
        return{
            block:'',
            street:'',
            city:''
        }
    }
}  

main.js

import Vue from 'vue';  
  
Vue.component('user-component', require'./user-component');  
Vue.component('contact-details', require'./contact-details');  

new Vue({
    el:'body'  
});  

index.html

...  
<body>
    <user-component></user-component>
        ...
</body>

我們正在顯示 phoneemail 資料,這些資料是 contact-detailsuser-component 的屬性,沒有電話或電子郵件資料。

將資料作為道具傳遞

因此,在模板屬性的 user-component.js 中,我們包含了 <contact-details> 元件,我們將手機電子郵件資料從 <user-component>(父元件)傳遞給 <contact-details>(子元件),方法是將它動態繫結到 props - :phone="phone":email="email,這是與 v-bind:phone="phone"v-bind:email="email" 相同

道具 - 動態繫結

由於我們動態繫結道具,因此父元件內的電話電子郵件中的任何更改即 <user-component> 將立即反映在子元件即 <contact-details> 中。

道具 - 作為文學

但是,如果我們將電話電子郵件的值作為字串文字值(如 phone="(44) 777 0007 0077" email="bond@mi6.com")傳遞,則它不會反映父元件中發生的任何資料更改。

單向繫結

預設情況下,更改方向是從上到下,即父元件中動態繫結道具的任何更改都將傳播到子元件,但子元件中 prop 值的任何更改都不會傳播到父元件。

例如:如果從 <contact-details> 中我們將電子郵件從 bond@mi6.com 更改為 jamesbond@mi6.com,則 <user-component> 中的父資料即電話資料屬性仍將包含 bond@mi6.com 的值。

但是,如果我們將父元件中的電子郵件的值從 bond@mi6.com 更改為 jamesbond@mi6.co(在我們的用例中為 <user-component>),則子元件中的電子郵件的值(在我們的用例中為 <contact-details>)將自動更改為 jamesbond@mi6.com - 父級中的更改為立即傳播給孩子。

雙向繫結

如果我們想要雙向繫結,那麼我們必須明確指定雙向繫結為:email.sync="email" 而不是:email="email"。現在,如果我們更改子元件中 prop 的值,則更改也將反映在父元件中。

在中型到大型應用程式中,從子狀態改變父狀態將非常難以檢測並且特別是在除錯時跟蹤 - 請謹慎

Vue.js 2.0 中不會有任何 .sync 選項。在 Vue.js 2.0 中不推薦使用 props 的雙向繫結

一次性繫結

也可以將顯式一次性繫結定義為:email.once="email,它或多或少類似於傳遞文字,因為父屬性值中的任何後續更改都不會傳播給子級。

CAVEAT
物件或陣列作為 prop 傳遞時,它們總是引用通過,這意味著無論明確定義:email.sync="email":email="email":email.once="email" 的繫結型別,如果電子郵件是父物件或陣列,則無論繫結型別如何,子元件中 prop 值的任何更改都將影響父元件中的值。

道具作為陣列

contact-details.js 檔案中,我們將 props:['phone', 'email'] 定義為一個陣列,如果我們不想使用道具進行細粒度控制,這很好。

道具作為物件

如果我們想要對道具進行更精細的控制,比如

  • 如果我們想要定義什麼型別的值可以接受作為道具
  • 什麼應該是道具的預設值
  • 是否必須為要求傳遞一個值(必需),還是可選的

然後我們需要使用物件表示法來定義道具,就像我們在 address.js 中所做的那樣。

如果我們正在創作可以由團隊中的其他開發人員使用的可重用元件,那麼將 props 定義為物件是一個很好的做法,這樣使用該元件的任何人都清楚地知道應該是什麼型別的資料以及是否它是強制性的或可選的。

它也被稱為道具驗證。該型別可以是以下預設建構函式中的任何一個:

  • 字串
  • 布林
  • 排列
  • 賓語
  • 功能
  • 或自定義建構函式

支援驗證的一些示例來自 http://vuejs.org/guide/components.html#Props

Vue.component('example', {
   props: {
       // basic type check (`null` means accept any type)
       propA: Number,
       // multiple possible types (1.0.21+)
       propM: [String, Number],
       // a required string
       propB: {
         type: String,
         required: true
       },
       // a number with default value
       propC: {
          type: Number,
          default: 100
       },
       // object/array defaults should be returned from a
       // factory function
       propD: {
          type: Object,
          default: function () {
             return { msg: 'hello' }
         }
       },
       // indicate this prop expects a two-way binding. will
       // raise a warning if binding type does not match.
       propE: {
          twoWay: true
       },
       // custom validator function
       propF: {
          validator: function (value) {
             return value > 10
          }
       },
       // coerce function (new in 1.0.12)
       // cast the value before setting it on the component
       propG: {
          coerce: function (val) {
            return val + '' // cast the value to string
          }
       },
       propH: {
          coerce: function (val) {
            return JSON.parse(val) // cast the value to Object
          }
       }
    }
});

camelCase vs kebab-case

HTML 屬性不區分大小寫,這意味著它無法區分 addresstypeaddressType,因此當使用 camelCase prop 名稱作為屬性時,我們需要使用它們的 kebab-case(連字元分隔)等價物:
addressType 應該在 HTML 屬性中寫為 address-type