在Vue中,父组件可以往子组件中传递一些属性值。而子组件需要使用props配置项定义这些要从父组件中获取的数据。

使用props配置项,可以让该组件的标签在被使用时,通过组件标签属性将值传入组件中。

例如:

 1<!-- SiteUser.vue -->
 2<template>
 3  <div>
 4    <h2>姓名:{{name}}</h2>
 5    <h2>年龄:{{age}}</h2>
 6    <h2>性别:{{sex}}</h2>
 7  </div>
 8</template>
 9<script>
10export default {
11  name: 'SiteUser',
12  // 声明需要接收的属性
13  props: [
14    'name',
15    'age',
16    'sex',
17  ],
18}
19</script>
 1<!-- App.vue -->
 2<template>
 3  <div>
 4    <!-- 
 5      在标签中,根据对应的 props 属性名称
 6      使用Html属性键值对的方式将值传入
 7     -->
 8    <SiteUser name="张三" age="21" sex="男"/>
 9    <hr>
10    <SiteUser name="李四" age="18" sex="女"/>
11    <hr>
12    <SiteUser name="王五" age="28" sex="男"/>
13  </div>
14</template>
15
16<script>
17  // 引入组件
18  import SiteUser from './components/SiteUser.vue';
19
20  export default {
21    name: 'app',
22    components: {
23      SiteUser,
24    },
25  }
26</script>

属性值的传递

在组件标签中,使用键值对将属性作为props属性传递给组件时,默认传入的数据类型是作为字符串类型。这意味着使用默认的方式无法将值作为其它类型进行传递。

要传递其它类型的值,可以使用v-bind指令。这是因为v-bind指令会将属性的值作为表达式去执行并计算值。所以使用了v-bind指令就以为着可以为props属性传入不同类型的值。

如,将上例中的age作为Number类型传入:

1<SiteUser name="张三" :age="20 + 1" sex="男"/>
2<SiteUser name="李四" :age="18" sex="女"/>
3<SiteUser name="王五" :age="28" sex="男"/>

定义方式

props有3种定义方式:

  • 字符串数组形式(简单声明):将所需传递的属性,作为字符串数组的元素进行声明。使用该方式进行声明的props属性,不限制传递的类型,不限制传递的必要性。

    如上例中:

    1props: [
    2  'name',
    3  'age',
    4  'sex',
    5],
    
  • 仅类型限制形式:在传递的同时进行类型限制。如传递的类型不匹配,控制台会报错。

    如上例,使用仅类型限制:

    1props: {
    2  name: String,
    3  age: Number,
    4  // age: [Number, String], // 允许指定多种类型
    5  sex: String,
    6},
    
  • 多限制形式:在传递的同时可以进行类型限制、必要性限制或默认值限制。

    如上例,使用多限制:

     1props: {
     2  name: {
     3    type: String,   // 类型限制
     4    required: true, // 必须传递(默认为 false)
     5  },
     6  age: {
     7    type: Number,
     8    default: 0,     // 设置默认值
     9  },
    10  sex: {
    11    type: String,
    12    required: true,
    13  }
    14},
    

    注:一般情况下,required设置为true时,就不使用default;在设置requiredrequiredfalse时,最好使用default

props属性是只读的,Vue底层会对props属性进行监测,并限制其修改。对props的修改不一定会失败,但是如果进行了修改,Vue会在控制台发出警告。

如果业务需求确实需要修改props属性,可以在data属性或计算属性中添加props属性对应的不重名的拷贝,然后在data属性或计算属性之上对数据进行修改。

例如:

 1<template>
 2  <div>
 3    <h2>姓名:{{myName}}</h2>
 4    <h2 v-if="myAge > 0">年龄:{{myAge}}</h2>
 5    <h2>性别:{{mySex}}</h2>
 6    <button @click="changeSex">修改性别</button>
 7  </div>
 8</template>
 9
10<script>
11export default {
12  name: 'SiteUser',
13  //  接收的同时对数据进行类型、必要性和默认值限制
14  props: {
15    name: {
16      type: String,   // 类型限制
17      required: true, // 必须传递(默认为 false)
18    },
19    age: {
20      type: Number,
21      default: 0,     // 设置默认值
22    },
23    sex: {
24      type: String,
25      required: true,
26    }
27  },
28  data() {
29    return {
30      // 可以重新定义不与 props 属性重名的 data 属性,以便操作数据
31      myName: this.name,
32      myAge: this.age,
33      mySex: this.sex,
34    }
35  },
36  methods: {
37    changeSex() {
38      // props 属性可以修改,但是控制台会报错,也过不了语法检查
39      // this.sex = this.sex === '男' ? '女' : '男'
40      this.mySex = this.mySex === '男' ? '女' : '男'
41    },
42  },
43}
44</script>

即,将props属性作为组件的初始化数据,对data属性或计算属性等内容进行初始化。组件中的数组载体还是以data属性或计算属性等为主。

注:

  • props属性同样不可与data属性或计算属性中的属性名称相同。
  • 如果props属性和data属性重名,props属性优先级更高。
  • props属性不能使用被Vue征用了的标签属性。
  • props属性不能修改是相对于组件而言的。在组件中,不要对当前组件的props属性进行修改,但是在使用组件时,可以对子组件的props属性通过v-bind指令进行动态数据绑定。
  • 不要使用v-model绑定props属性。

子组件到父组件的数据通信

组件间数据的通信涉及以下内容:

  • 父组件到子组件的数据通信。
  • 子组件到父组件的数据通信。
  • 同级组件间的数据通信。

父组件到子组件的通信直接通过props属性就可以实现。而子组件到父组件的通信,也可以通过props实现。

修改对象类型的 Prop

Vue对props属性是一种浅层次的监测,Vue不会对props属性中的属性的改动进行监测。利用这一点可以在子组件中通过获取对象类型props属性值,然后利用v-model绑定该对象的属性来越过Vue的监测,从而实现子组件到父组件的通信。

 1<!-- 
 2  user-input.vue
 3 -->
 4
 5<template>
 6<div>
 7  <!-- 使用 v-model 直接修改 props 对象数据类型的属性中的属性 -->
 8  <input 
 9    type="text" 
10    v-model.lazy="user.account"
11    placeholder="账号">
12  <br><br>
13  <input 
14    type="password" 
15    v-model.lazy="user.password"
16    placeholder="密码">
17</div>
18</template>
19
20<script>
21export default {
22  name: 'user-input',
23  props: [
24    "userInfo", // 将数据对象定义在父组件中
25  ],
26  data() {
27    return {
28      // 利用 props 属性初始化,目的是防止报错
29      user: this.userInfo,
30    }
31  },
32}
33</script>
 1<!-- 
 2  App.vue
 3 -->
 4
 5<template>
 6<div>
 7  <user-input :userInfo="userInfo"/>
 8  <br>
 9  <button @click="login">登录</button>
10</div>
11</template>
12
13<script>
14import UserInput from './components/user-input.vue'
15
16export default {
17  name: 'app',
18  components: {
19    UserInput,
20  },
21  data() {
22    return {
23      userInfo: {
24        account: '',
25        password: '',
26      }
27    }
28  },
29  methods: {
30    login() {
31      alert(`账号:${this.userInfo.account}\n密码:${this.userInfo.password}`)
32    },
33  },
34}
35</script>

该方法其实就是子组件通过props,将用户在子组件中输入的数据,通过v-model双向绑定到父组件上。

不建议使用这种方法实现,虽然修改对象类型的props属性中的属性不会报错(绕过了Vue的监测),但是这样操作违反了Vue的原则。

传递函数类型的 Prop

借助props将父组件中的某些方法传递到子组件中,然后在子组件中通过调用这些从父组件传递过来的props方法来实现子组件到父组件的通信。

例如有一个user-input组件来收集用户的账号和密码信息,而App.vue需要获取user-input组件收集到的用户信息:

 1<!-- 
 2  user-input.vue
 3 -->
 4
 5<template>
 6<div>
 7  <input 
 8    type="text" 
 9    v-model.lazy="userInfo.account"
10    placeholder="账号">
11  <br><br>
12  <input 
13    type="password" 
14    v-model.lazy="userInfo.password"
15    placeholder="密码">
16</div>
17</template>
18
19<script>
20export default {
21  name: 'user-input',
22  props: [
23    "setUserInfo",  // 从父组件接收一个用于通知父组件数据更新的函数
24  ],
25  data() {
26    return {
27      userInfo: {
28        account: '',
29        password: '',
30      }
31    }
32  },
33  watch: {
34    // 监听 userInfo 对象
35    userInfo: {
36      deep: true,
37      handler() {
38        // 通知父组件 userInfo 被更新
39        this.setUserInfo(this.userInfo)
40      },
41    },
42  },
43}
44</script>
 1<!-- 
 2  App.vue
 3 -->
 4
 5<template>
 6<div>
 7  <!-- 将通知信息更新的函数传递给组件 -->
 8  <user-input :setUserInfo="setUserInfo"/>
 9  <br>
10  <!-- 点击登录按钮显示用户信息 -->
11  <button @click="login">登录</button>
12</div>
13</template>
14
15<script>
16import UserInput from './components/user-input.vue'
17
18export default {
19  name: 'app',
20  components: {
21    UserInput,
22  },
23  data() {
24    return {
25      userInfo: {
26        account: '',
27        password: '',
28      }
29    }
30  },
31  methods: {
32    // 通知组件用户信息被更新
33    setUserInfo(userInfo) {
34      this.userInfo.account = Object.hasOwn(userInfo, "account") ?
35                              userInfo.account : ""
36      this.userInfo.password = Object.hasOwn(userInfo, "password") ?
37                              userInfo.password : ""
38    },
39    login() {
40      alert(`账号:${this.userInfo.account}\n密码:${this.userInfo.password}`)
41    },
42  },
43}
44</script>

该方法的原理就是:被父组件传递给子组件用于更新的函数,它的this指向的仍是父组件的实例对象。简单来说就是子组件调用了一个父组件的方法来通知父组件