在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
;在设置required
或required
为false
时,最好使用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
指向的仍是父组件的实例对象。简单来说就是子组件调用了一个父组件的方法来通知父组件。
评论