使用 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