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

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

例如:

<!-- SiteUser.vue -->
<template>
  <div>
    <h2>姓名:{{name}}</h2>
    <h2>年龄:{{age}}</h2>
    <h2>性别:{{sex}}</h2>
  </div>
</template>
<script>
export default {
  name: 'SiteUser',
  // 声明需要接收的属性
  props: [
    'name',
    'age',
    'sex',
  ],
}
</script>
<!-- App.vue -->
<template>
  <div>
    <!-- 
      在标签中,根据对应的 props 属性名称
      使用Html属性键值对的方式将值传入
     -->
    <SiteUser name="张三" age="21" sex="男"/>
    <hr>
    <SiteUser name="李四" age="18" sex="女"/>
    <hr>
    <SiteUser name="王五" age="28" sex="男"/>
  </div>
</template>

<script>
  // 引入组件
  import SiteUser from './components/SiteUser.vue';

  export default {
    name: 'app',
    components: {
      SiteUser,
    },
  }
</script>

属性值的传递

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

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

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

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

定义方式

props有3种定义方式:

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

    如上例中:

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

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

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

    如上例,使用多限制:

    props: {
      name: {
        type: String,   // 类型限制
        required: true, // 必须传递(默认为 false)
      },
      age: {
        type: Number,
        default: 0,     // 设置默认值
      },
      sex: {
        type: String,
        required: true,
      }
    },
    

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

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

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

例如:

<template>
  <div>
    <h2>姓名:{{myName}}</h2>
    <h2 v-if="myAge > 0">年龄:{{myAge}}</h2>
    <h2>性别:{{mySex}}</h2>
    <button @click="changeSex">修改性别</button>
  </div>
</template>

<script>
export default {
  name: 'SiteUser',
  //  接收的同时对数据进行类型、必要性和默认值限制
  props: {
    name: {
      type: String,   // 类型限制
      required: true, // 必须传递(默认为 false)
    },
    age: {
      type: Number,
      default: 0,     // 设置默认值
    },
    sex: {
      type: String,
      required: true,
    }
  },
  data() {
    return {
      // 可以重新定义不与 props 属性重名的 data 属性,以便操作数据
      myName: this.name,
      myAge: this.age,
      mySex: this.sex,
    }
  },
  methods: {
    changeSex() {
      // props 属性可以修改,但是控制台会报错,也过不了语法检查
      // this.sex = this.sex === '男' ? '女' : '男'
      this.mySex = this.mySex === '男' ? '女' : '男'
    },
  },
}
</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的监测,从而实现子组件到父组件的通信。

<!-- 
  user-input.vue
 -->

<template>
<div>
  <!-- 使用 v-model 直接修改 props 对象数据类型的属性中的属性 -->
  <input 
    type="text" 
    v-model.lazy="user.account"
    placeholder="账号">
  <br><br>
  <input 
    type="password" 
    v-model.lazy="user.password"
    placeholder="密码">
</div>
</template>

<script>
export default {
  name: 'user-input',
  props: [
    "userInfo", // 将数据对象定义在父组件中
  ],
  data() {
    return {
      // 利用 props 属性初始化,目的是防止报错
      user: this.userInfo,
    }
  },
}
</script>
<!-- 
  App.vue
 -->

<template>
<div>
  <user-input :userInfo="userInfo"/>
  <br>
  <button @click="login">登录</button>
</div>
</template>

<script>
import UserInput from './components/user-input.vue'

export default {
  name: 'app',
  components: {
    UserInput,
  },
  data() {
    return {
      userInfo: {
        account: '',
        password: '',
      }
    }
  },
  methods: {
    login() {
      alert(`账号:${this.userInfo.account}\n密码:${this.userInfo.password}`)
    },
  },
}
</script>

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

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

传递函数类型的 Prop

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

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

<!-- 
  user-input.vue
 -->

<template>
<div>
  <input 
    type="text" 
    v-model.lazy="userInfo.account"
    placeholder="账号">
  <br><br>
  <input 
    type="password" 
    v-model.lazy="userInfo.password"
    placeholder="密码">
</div>
</template>

<script>
export default {
  name: 'user-input',
  props: [
    "setUserInfo",  // 从父组件接收一个用于通知父组件数据更新的函数
  ],
  data() {
    return {
      userInfo: {
        account: '',
        password: '',
      }
    }
  },
  watch: {
    // 监听 userInfo 对象
    userInfo: {
      deep: true,
      handler() {
        // 通知父组件 userInfo 被更新
        this.setUserInfo(this.userInfo)
      },
    },
  },
}
</script>
<!-- 
  App.vue
 -->

<template>
<div>
  <!-- 将通知信息更新的函数传递给组件 -->
  <user-input :setUserInfo="setUserInfo"/>
  <br>
  <!-- 点击登录按钮显示用户信息 -->
  <button @click="login">登录</button>
</div>
</template>

<script>
import UserInput from './components/user-input.vue'

export default {
  name: 'app',
  components: {
    UserInput,
  },
  data() {
    return {
      userInfo: {
        account: '',
        password: '',
      }
    }
  },
  methods: {
    // 通知组件用户信息被更新
    setUserInfo(userInfo) {
      this.userInfo.account = Object.hasOwn(userInfo, "account") ?
                              userInfo.account : ""
      this.userInfo.password = Object.hasOwn(userInfo, "password") ?
                              userInfo.password : ""
    },
    login() {
      alert(`账号:${this.userInfo.account}\n密码:${this.userInfo.password}`)
    },
  },
}
</script>

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