组件间的通信
在开发过程中,经常遇到需要组件之间相互进行通信:
比如App可能使用了多个Header,每个地方的Header展示的内容不同,那么就需要使用者传递给Header进行展示
又比如在 main 中一次性请求了 Banner 数据和 ProductList 数据,那么就需要父组件传递给它们来进行展示
也可能是子组件中发生了事件,需要由父组件来完成某些操作,那就需要子组件向父组件传递事件
父组件传递给子组件---通过 props,props:接收父组件传递过来的属性 子组件传递给父组件---通过 $emit 触发事件
组件的嵌套关系
如果一个应用程序将所有的逻辑都放在一个组件中,那么这个组件就会变成非常的臃肿和难以维护
所以组件化的核心思想就是对组件进行拆分,拆分成一个个小的组件,再将这些组件组合嵌套在一起,最终形成我们的应用程序
类型定义
在传入数据之前,我们需要对数据进行一个类型定义,关于类型定义有两种写法:数组写法和对象写法(常用)。
- 数组写法(在实际开发中并不常见---不能对数据类型进行验证):
export default {
name: "ShowInfo",
props: ["name", "age", 'height']
}
- 对象写法
type:类型 required:必填 default:默认值
export default {
name: "ShowInfo",
//对象写法
props: {
name: {
type: String,
required: true,
},
//注:如果 type 是一个对象或者数组,那么必须写成一个函数!!!
propsA: {
type: Object,
//default:()=>({name:'zhangsan'})
default() {
return {message: 'hello'}
}
},
propsG: {
//写组件库或者框架时需要
validator(value) {
//这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].includes(value)
}
}
},
}
- 细节一:
type的类型有:String、Number、Boolean、Array、Object、Date、Function、Symbol
- 细节二:对象类型的其他写法
export default{
props:{
messagteInfo:{
type:String
},
//基础的类型检查('null'和‘undefined’会通过任何类型验证)
paopA:{
type:Number
},
paopB:[String, Number],
propC:{
type:String,
required:true
},
propE:{
type:Object,
default(){
return {message: 'hello'}
}
},
//自定义验证函数
propF:{
validator(value){
//这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].includes(value)
}
},
propG:{
type:Function,
default(){
return "Default function"
}
}
}
}
细节三:Props的大小写命名
HTML中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当使用DOM中的模板时, camelCase 的 prop 名需要使用其等价的 kebab-case 命名
父传子
案例:
<!--App:父组件-->
<template>
<div>
<!--这里的 address 和 class 是传递到子组件的内容-->
<show-info name="why" :age="18" :height="1.88" address="广州" class="active"/>
<show-info name="kebi" :age="20" :height="1.88"/>
<show-info name="kebi" :age="20" :height="1.88" show-msg="jafls;jas;f"/>
</div>
</template>
<script>
import showInfo from "./ShowInfo.vue";
export default {
components: {showInfo},
setup() {
return {showInfo}
}
}
</script>
<!--showInfo:子组件-->
<template>
<div class="infos">
<!--不继承就传递到下面绑定 $attrs 的元素上,继承就传递到根元素上-->
<h2 :class="$attrs">姓名:{{ name }}</h2>
<h2>年龄:{{ age }}</h2>
<h2>身高:{{ height }}</h2>
<h2>信息:{{ showMsg }}</h2>
</div>
</template>
<script>
export default {
name: "ShowInfo",
//不继承
inheritAttrs: false,
//props:接收父组件传递过来的属性
//数组写法---弊端:不能对数据类型进行验证
//props: ["name", "age", 'height']
//对象写法
props: {
name: {
type: String,
required: true,
},
age: {
type: Number,
required: true
},
height: {
type: Number,
required: true
},
showMsg: {
type: String,
default: '啊哈哈哈'
}
}
}
</script>
非 props 的 attribute
当我们传递给一个组件某个属性,但是该属性并没有定义对应的 props 时,就称之为非 Prop 的 attribute,常见的包括class、style、id属性等
如果当前的属性是一个非 Prop 的 attribute,那么该属性会默认添加到子组件的根元素上
如果不希望组件的根元素继承 attribute,可以在组件中设置 inheritAttrs:false
inheritAttrs:false
禁用 attribute 继承的常见情况是需要将 attribute 应用与根元素之外的其他元素
可以通过 $attrs 来访问所有非 props 的 attribute
<!--这样就将父组件的数据放到了姓名这里了-->
<h2 :class="$attrs">姓名:{{name}}</h2>
多个根节点的attribute:多个根节点的 attribute 如果没有显示的绑定,那么会报警告,必须手动的指定要绑定到哪一个属性上
总结
父组件传属性到子组件需要一下几个步骤:
- 首先,需要定义数据类型(传props)
- 其次,思考父组件传递到子组件的属性是否可继承(是否是根元素上)
- 如果可继承,那么就直接传递到根元素上了(默认)
- 如果不可继承需要加 inheritAttrs:false,然后使用 $attrs 来访问所有非 props 的属性
子组件传父组件
- 子组件需要传递内容到父组件的情况
当子组件有一些事件发生的时候,比如在组件中发生了点击,父组件需要切换内容
子组件有一些内容想要传递给父组件的时候
-
如何完成
- 首先,需要在子组件中定义好在某些情况下触发的事件名称
- 其次,在父组件中以 v-on 的方式传入要监听的事件名称,并且绑定到对应的方法中
- 最后,在子组件中发生某个事件的时候,根据事件名称触发对应的事件
-
自定义事件的事件的流程
- 封装一个CounterOperation组件
- 内部其实是监听两个按钮的点击,点击之后通过 this.$emit(vue2) 或 context.text 或 defineEmits(语法糖) 的方式发出去事件
<!--App:父组件-->
<template>
<div>
<h2>当前计数:{{ counter }}</h2>
<AddCounter @add="addBtnClick"/>
<SubCounter @sub="subBtnClick"/>
</div>
</template>
<script>
import AddCounter from "./AddCounter.vue";
import SubCounter from "./SubCounter.vue";
import {ref} from "vue";
export default {
components: {AddCounter, SubCounter},
//emits: ['onClick'],
setup() {
const counter = ref(0)
function addBtnClick(count) {
//console.log(counter)
counter.value += count
}
function subBtnClick(count){
counter.value -= count
}
return {AddCounter, SubCounter, counter, addBtnClick,subBtnClick}
}
}
</script>
<template>
<!--AddCounter:子组件-->
<div class="add">
<!--让子组件事件发出去一个自定义事件-->
<button @click="btnClick(1)">+1</button>
<button @click="btnClick(5)">+5</button>
<button @click="btnClick(10)">+10</button>
<!--其他写法-->
<!--<button @click="$emit('onAdd',1)">+1</button>-->
<!--<button @click="$emit('onAdd',5)">+5</button>-->
<!--<button @click="$emit('onAdd',10)">+10</button>-->
</div>
</template>
<script>
export default {
name: "AddCounter",
//这里的 props 不能因为只写 context 就不写!!!
setup(props, context) {
//第一个参数是自定义的事件名称
//第二个参数是传递的参数
const btnClick = (count) => {
//console.log(context.emit)
context.emit('add',count)
}
return {btnClick}
}
}
</script>
<!--SubCounter:子组件-->
<template>
<div class="sub">
<button @click="btnClick(1)">-1</button>
<button @click="btnClick(5)">-5</button>
<button @click="btnClick(10)">-10</button>
</div>
</template>
<script>
export default {
name: "SubCounter",
setup(props, context) {
const btnClick = (count) => {
context.emit('sub', count)
}
return {btnClick}
}
}
</script>
使用语法setup语法糖
以上案例都没有使用 setup 语法糖,接下来展示使用setup语法糖展示
父传子
<!--APP:父组件-->
<template>
<div>
<show-info_setup name="why" :age="18" :height="1.88" address="广州" class="active"/>
<show-info_setup name="kebi" :age="20" :height="1.88"/>
<show-info_setup name="kebi" :age="20" :height="1.88" show-msg="jafls;jas;f"/>
</div>
</template>
<script setup>
import ShowInfo_setup from "./ShowInfo_setup.vue";
</script>
<template>
<div class="infos">
<h2 :class="$attrs">姓名:{{ name }}</h2>
<h2>年龄:{{ age }}</h2>
<h2>身高:{{ height }}</h2>
<h2>信息:{{ showMsg }}</h2>
</div>
</template>
<script setup>
const props = defineProps({
name: {
type: String,
required: true,
},
age: {
type: Number,
required: true
},
height: {
type: Number,
required: true
},
showMsg: {
type: String,
default: '啊哈哈哈'
}
})
</script>
子传父
<!--APP;父组件-->
<template>
<div>
<h2>当前计数:{{ counter }}</h2>
<AddCounter_setup @add="addBtnClick"/>
</div>
</template>
<script setup>
import AddCounter_setup from "./AddCounter_setup.vue";
import {ref} from "vue";
const counter=ref(0)
const addBtnClick=(count)=>{
counter.value+=count
}
</script>
<!--子组件-->
<template>
<button @click="btnClick(1)">+1</button>
<button @click="btnClick(5)">+5</button>
<button @click="btnClick(10)">+10</button>
</template>
<script setup>
const emits = defineEmits(['add'])
const btnClick = (count) => {
emits('add', count)
}
</script>