前言:
在开发工程中,我们经常会遇到组件之间相互进行通信,比如在一个组件内使用多个Button,每个地方的Button展示内容不同,执行方法不同,那么我们就需要该组件传递给Button一些数据,让Button组件进行展示。又比如子组件中发生一些事件,需要由父组件来完成某些操作来修改父组件里的值,那么需要子组件向父组件传递事件以及一些参数。这儿记录一下 Vue3 中组件之间通信之props。
父子组件之间的通信方式:
- 父组件传递给子组件: 通过 props 属性;
- 子组件传递给父组件: 通过 $emit 触发事件;
首先先看看父组件是如何向子组件传递参数的?
什么是props?
- props 是通过在组件上注册一些自定义的属性(attribute);
- 通过父组件给这些属性(attribute)赋值,或者通过v-bind绑定父组件中data中的值,子组件再通过属性的名称来获取对应的值;
字符串数组用法举例:
// App.vue
<template>
<Child :firstName="firstName" :lastName="lastName" v-bind="info"/>
// 当需要传递的值为对象时,可以直接通过bind绑定,也可以分开通过属性名进行绑定。
<Child v-bing="info"></Child>
<Child :age="info.age" :height="height"></Child>
</template>
<script>
import Child from './components/Child.vue'
export default {
name: 'App',
components: {
Child
},
data() {
return {
firstName: '哈喽',
lastName: '欢迎您!'
info: {
age: "18岁",
height: '188cm'
}
}
}
}
</script>
下方是子组件:
// Child.vue
<template>
<div>
<h3>{{ firstName + ',' + lastName}}</h3>
<h3>{{info.age + '的我今年身高' + info.height}}</h3>
</div>
</template>
<script>
export default {
name: 'Child',
props: ["firstName", "lastName", "age", "height"]
}
</script>
通过对象绑定时,父组件可以直接使用 v-bind=“对象名” 进行传值,那么子组件同样通过对象里的属性名进行取值。
数组用法中我们只能说明传入的属性的名称,而不能对这些属性值进行约束。
对象类型举例:
// Child.vue
<template>
<div>
<h3>{{ firstName + ',' + lastName}}</h3>
<h3>{{age + '的我今年身高' + height}}</h3>
</div>
</template>
<script>
export default {
name: 'Child',
props: {
firstName: String,
lastName: String,
age: String,
height: {
type: String, // 传入的类型
required: true, // required 为true表示该属性为必传选项 (required和default分开使用)
default: "Hello", // 默认值,当该属性没有进行传值,该属性默认值为"Hello"
}
}
}
</script>
如果传入的值不符合约束类型,那么在浏览器中就会报出警告:该属性希望得到一个String类型的值,实际上得到是一个Number类型值。
通过对象形式定义props,不仅可以通过type属性来约束值的类型,还可以控制指定传入的属性是否是必传的,当属性没有传入值时属性的默认值。
下面我们再来看看type还有哪些类型?
- String
- Number
- Boolean
- Array
- Object
- Data
- Function
- Symbol
下面简单写一下各个类型的写法:(自己也顺便加深下印象)
props: {
obj: { // 带有默认值的对象
type: Object,
default() { //这儿类型为Object必须通过一个工厂函数获取,类似于data(){},防止堆地址相同
return { grade: 90}
}
},
funC: {
type: Function,
default() {
return "Default function" // 默认返回一个函数
}
},
numB: { // 带有默认值的数字
type: Number,
default: 100
},
str: { // 带有默认值的字符串
type: String,
default: 'heihei'
}
}
从上面可以看出,props有两种常见的用法:
- 字符串数组,数组中的字符串就是属性(attribute)的名称;(无法对传进来的值进行约束)
- 对象类型,对象类型我们可以在指定属性(attribute)名称的同时,指定它需要传递的类型、默认值、以及是否必须等等;
$attrs
当我们在父组件向子组件传递了某个属性,但是在子组件中并没有通过props取值时,那么可以通过this.$attrs来获取,比较常见的是class、style、id属性等等;
举个例子:
// 父组件(父组件向子组件传递了4个属性值)
<Child :firstName="firstName" :lastName="lastName" :age="info.age" :height="info.height"></Child>
// 子组件
<template>
<div>
<h3>{{ firstName + ',' + lastName}}</h3>
</div>
</template>
<script>
export default {
name: 'Child',
props: {
firstName: String,
lastName: String
},
created() {
console.log(this.$attrs) // Proxy {age: "18岁", height: "188cm", __vInternal: 1}
}
}
</script>
所以,我们可以直接在子组件上这样写
<template>
<div>
<h3>{{ firstName + ',' + lastName}}</h3>
<h3>{{ $attrs.age + '的我今年身高' + $attrs.height}}</h3>
</div>
</template>
<script>
export default {
name: 'Child',
props: {
firstName: String,
lastName: String,
}
}
</script>
通过$attrs一样能够使用父组件传递过来的值。注意:($attrs里包含的属性是在props 中没有定义的属性)
当我们在父组件中传递了一个class,我们看看会发生什么?
// App.vue
<Child :firstName="firstName" :lastName="lastName" class="content"></Child>
子组件中:
<template>
<div>
<h3>{{ firstName + ',' + lastName}}</h3>
<h3>{{ $attrs.age + '的我今年身高' + $attrs.height}}</h3>
</div>
</template>
<script>
export default {
name: 'Child',
props: {
firstName: String,
lastName: String,
}
}
</script>
<style scoped>
.content {
color: red;
}
</style>
当父组件再加一个属性,子组件props依然只定义(firstName, lastName)如下:
// App.vue
<Child :firstName="firstName" :lastName="lastName" :age="info.age" class="content"></Child>
我们会发现,子组件中的字体颜色变为红色,age属性加在了组件的根节点上。(我们知道template是不会加在到dom上的)
那如果不想要这个属性继承到根节点上,怎么办呢?
那什么时候才使用inheritAttrs,当我们希望这个属性不被根节点继承,而是需要其他节点使用这个属性,那么就可以通过设置inheritAttrs为false,然后上面我们知道了$attrs可以获取props属性以外的其他属性,那么通过$attrs来给其他节点进行绑定:
// App.vue
<Child :firstName="firstName" :lastName="lastName" v-bind="info" class="content"></Child>
子组件中:
// Child.vue
<template>
<div>
<h3 :class="$attrs.class">{{ firstName + ',' + lastName}}</h3>
<h3>{{ $attrs.age + '的我今年身高' + $attrs.height}}</h3>
</div>
</template>
<script>
export default {
inheritAttrs: false,
name: 'Child',
props: {
firstName: String,
lastName: String,
}
}
</script>
结果:
还有一种情况,在vue3中可以允许有多个根节点,那么当在多个根节点时,没有通过$attrs进行绑定,那么在浏览器中会报警告:
// App.vue
<Child :firstName="firstName" :lastName="lastName" v-bind="info" class="content"></Child>
// Child.vue
<template>
<h3>{{ firstName + ',' + lastName}}</h3>
<h3>12312</h3>
</template>
<script>
export default {
name: 'Child',
props: {
firstName: String,
lastName: String,
}
}
</script>
所以,这种情况,我们必须通过$attrs来手动绑定后,才会消除警告。
// 修改Child.vue
<template>
<h3 :class="$attrs.class">{{ firstName + ',' + lastName}}</h3>
<h3>12312</h3>
</template>
总结:
以上就是我对父传子props的理解,记录一下,后面再总结一下$emit的用法。