组件通信
- 一、props
- 二、自定义事件
- 三、mitt
- 四、v-model
- 五、$attrs
- 六、$refs 和 $parent
- 七、provide,inject
- 八、pinia
- 九、插槽
- 默认插槽
- 具名插槽
- 作用域插槽
- 总结
一、props
概述:props是使用频率最高的一种通信方式,常用与:父<—>子
- 若父传子:属性值是非函数
- 若子传父:属性值是函数
<template>
<div class="main-div">
<div class="father-div ">
<h2>父组件</h2>
<h3>我有一辆{{ car }}车</h3>
<h3>儿子的玩具{{toy}}</h3>
<ProsSon :car="car" :sendToy="getToy"/>
</div>
</div>
</template>
<script lang="ts" setup>
import ProsSon from './prosSon.vue'
import {ref} from 'vue'
let car = ref("宝马")
let toy = ref()
function getToy(value:string){
toy.value = value
}
</script>
<style scoped>
.main-div {
display: flex;
justify-content: center;
align-items: center;
}
.father-div {
width: 600px;
height: auto;
background: orange;
display: flex;
justify-content: top;
align-items: center;
flex-direction: column;
border: solid gray 1px;
border-radius: 15px;
}
</style>
<template >
<div class="son-div">
<h2>子组件</h2>
<h3>我有一个:{{toy}}</h3>
<h3>老爸有一辆{{car}}</h3>
<button @click="sendToy(toy)">把玩具给父亲</button>
</div>
</template>
<script lang="ts" setup>
import {ref} from 'vue'
//数据
let toy = ref("奥特曼")
//声明接受props
defineProps(["car","sendToy"])
</script>
<style scoped>
.son-div{
background-color: aquamarine;
width: 500px;
height: 250px;
margin: 10px;
border: solid gray 1px;
border-radius: 15px;
}
</style>
二、自定义事件
概述:自定义事件常用于:子 => 父。
示例:
<template>
<div class="father">
<h2>父组件</h2>
<h3>儿子玩具:{{ toy }}</h3>
<!-- 在父组件中,给子组件绑定自定义事件 -->
<Child @send-toy="saveToy"></Child>
</div>
</template>
<script lang="ts" setup>
import Child from './Child.vue';
import {ref} from 'vue'
let toy = ref()
function saveToy(value:string){
toy.value = value
}
</script>
<style scoped>
.father {
background: gray;
padding: 10px;
box-shadow: 0 0 10px black;
border-radius: 10px;
margin: 0 10px;
}
</style>
<template>
<div class="child">
<h3>子组件</h3>
<h4>玩具:{{ toy }}</h4>
<button @click="$emit('send-toy',toy)">把玩具给父亲</button>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
let toy = ref("奥特曼")
defineEmits(['send-toy'])
</script>
<style scoped>
.child{
background-color: rgb(76, 209, 76);
padding: 10px;
box-shadow: 0 0 10px black;
border-radius: 10px;
}
</style>
三、mitt
概述:与消息订阅与发布(pubsub
)功能类似,可以实现任意组件间通信。
安装
npm i mitt
测试一下,在utils/emitter.ts文件中写入如下代码:
//引入mitt
import mitt from 'mitt'
//调用mitt得到emitter,emitter能绑定事件,触发事件
const emitter = mitt()
//绑定事件示例
emitter.on('test1',()=>{
console.log("test1被调用了")
})
//触发事件示例
emitter.emit('test1')
//解绑事件示例
emitter.off('test1')
//暴露emitter
export default emitter
接收数据的组件中:绑定事件、同时在销毁前解绑事件:
import emitter from "@/utils/emitter";
import { onUnmounted } from "vue";
// 绑定事件
emitter.on('send-toy',(value)=>{
console.log('send-toy事件被触发',value)
})
onUnmounted(()=>{
// 解绑事件
emitter.off('send-toy')
})
【第三步】:提供数据的组件,在合适的时候触发事件
import emitter from "@/utils/emitter";
function sendToy(){
// 触发事件
emitter.emit('send-toy',toy.value)
}
注意这个重要的内置关系,总线依赖着这个内置关系
四、v-model
1、概述:实现父与子之间互相通信。
2、前序知识—— v-model
的本质
<!-- 使用v-model指令 -->
<input type="text" v-model="userName">
<!-- v-model的本质是下面这行代码 -->
<input
type="text"
:value="userName"
@input="userName =(<HTMLInputElement>$event.target).value"
>
3、组件标签上的 v-model
的本质::moldeValue
+ update:modelValue
事件。
<SelfInput v-model="userName"/>
<!-- 组件标签上v-model 的本质 -->
<SelfInput :model-value="userName" @update:model-value="userName = $event"></SelfInput>
SelfInput
组件中:
<template>
<div class="box">
<input type="text" :value="modelValue" @input="emit('update:model-value',(<HTMLInputElement>$event.target).value)"/>
</div>
</template>
<script setup lang="ts">
//接受props
defineProps(['modelValue'])
//声明事件
const emit = defineEmits(['update:model-value'])
</script>
4、 也可以更换value
,例如改为abc
<!-- 也可以更换value,例如改成abc-->
<SelfInputTemp v-model:abc="password"></SelfInputTemp>
<!-- 上面代码的本质如下 -->
<SelfInputTemp :abc="password" @update:abc="password = $event" />
SelfInputTemp
组件中
<template>
<div class="box">
<input type="text" :value="abc" @input="emit('update:abc',(<HTMLInputElement>$event.target).value)"/>
</div>
</template>
<script setup lang="ts">
defineProps(["abc"])
const emit = defineEmits(['update:abc'])
</script>
5、如果value
可以更换,那么就可以在组件上多次使用v-model
<SelfInput v-model:abc='abc' v-model:xyx='xyz' />
五、$attrs
1、概述:$attrs
用于实现当前组件的父组件,向当前组件的子组件通过(祖 -> 孙)
2、具体说明:$attrs
是一个对象,包含所有父组件传入的标签属性
注意:
$attrs
会自动排除props
中声明的属性(可以认为声明过的props
被子组件自己消费了)
父组件
<template>
<div class="father">
<h2>父组件</h2>
<Child :a="a" :b="b" :c="c" v-bind="{x:1,y:2}" :updateA="updateA" ></Child>
</div>
</template>
<script setup lang="ts">
import Child from './Child.vue';
import {ref} from 'vue'
let a = ref(1)
let b = ref(2)
let c = ref(3)
function updateA(value : number){
a.value = value
}
</script>
<style scoped>
.father {
background: #ffe8e8;
padding: 10px;
box-shadow: 0 0 10px black;
border-radius: 10px;
margin: 0 10px;
}
</style>
子组件
<template>
<div class="child">
<h2>子组件</h2>
<GrandChild v-bind="$attrs"/>
</div>
</template>
<script setup lang="ts">
import GrandChild from './GrandChild.vue';
</script>
<style scoped>
.child {
background-color: rgb(76, 209, 76);
padding: 10px;
box-shadow: 0 0 10px black;
border-radius: 10px;
margin-top: 20px;
}
</style>
孙组件
<template>
<div class="GrandChild">
<h3>孙子组件</h3>
<h4>a:{{ a }}</h4>
<h4>b:{{ b }}</h4>
<h4>c:{{ c }}</h4>
<h4>x:{{ x }}</h4>
<h4>y:{{ y }}</h4>
<button @click="updateA(666)">点我更新</button>
</div>
</template>
<script setup lang="ts">
defineProps(['a','b','c','x','y','updateA'])
</script>
<style scoped>
.GrandChild {
background-color: rgb(62, 142, 222);
padding: 10px;
box-shadow: 0 0 10px black;
border-radius: 10px;
margin-top: 20px;
}
</style>
六、$refs 和 $parent
- 概述:
$refs 用于: 父->子.
$parent 用于 子->父。
父组件:
<template>
<div class="father">
<h2>父组件</h2>
<h3>房产:{{ house }}</h3>
<button @click="changeToy">修改c1玩具</button>
<button @click="changeComputer">修改c2电脑</button>
<button @click="sendBooks($refs)">发书</button>
<Child1 ref="c1" />
<Child2 ref="c2" />
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import Child1 from './Child1.vue'
import Child2 from './Child2.vue';
const house = ref(4)
const c1 = ref()
const c2 = ref()
function changeToy() {
c1.value.toy = "黑神话:悟空"
}
function changeComputer(){
c2.value.computer = "华为"
}
function sendBooks(refs: { [key: string]: any; }){
for(let key in refs){
refs[key].books +=3
}
}
//向外暴露house
defineExpose({house})
</script>
<style scoped>
.father {
background: gray;
padding: 10px;
box-shadow: 0 0 10px black;
border-radius: 10px;
margin: 0 10px;
}
</style>
组件一
<template>
<div class="child">
<h2>子组件1</h2>
<h3>玩具:{{ toy }}</h3>
<h3>书:{{ books }} 本</h3>
<button @click="getHosue($parent)">给我一套房</button>
</div>
</template>
<script lang="ts" setup>
import {ref} from 'vue'
let toy = ref('孙悟空')
let books = ref(3)
function getHosue(parent: any){
parent.house -= 1
}
defineExpose({toy,books})
</script>
<style scoped>
.child {
background-color: rgb(76, 209, 76);
padding: 10px;
box-shadow: 0 0 10px black;
border-radius: 10px;
margin-top: 20px;
}
</style>
组件二
<template>
<div class="child">
<h2>子组件2</h2>
<h3>电脑:{{ computer }}</h3>
<h3>书:{{ books }} 本</h3>
</div>
</template>
<script lang="ts" setup>
import {ref} from 'vue'
let computer = ref('华硕')
let books = ref(4)
defineExpose({computer,books})
</script>
<style scoped>
.child {
background-color: rgb(76, 209, 76);
padding: 10px;
box-shadow: 0 0 10px black;
border-radius: 10px;
margin-top: 20px;
}
</style>
七、provide,inject
- 概述:实现祖孙组件直接通信
- 具体使用
- 在祖先组件中通过provide 配置向后代组件提供数据
- 在后代组件中通过inject配置来声明接收数据
- 具体编码:
父组件:
<template>
<div class="father">
<h2>父组件</h2>
<h3>银子:{{ money }}</h3>
<h3>车子:一辆{{ car.brank }}车,价值{{ car.price }}万元</h3>
<Child></Child>
</div>
</template>
<script setup lang="ts">
import Child from './Child.vue';
import {ref,reactive,provide} from 'vue'
let money = ref(100)
let car = reactive({
brank:'奇瑞',
price:'14'
})
function updateMoney(value:number) {
money.value -= value
}
//向其后代提供数据
provide('moneyContext',{money,updateMoney})
provide('car',car)
</script>
<style scoped>
.father {
background: #ffe8e8;
padding: 10px;
box-shadow: 0 0 10px black;
border-radius: 10px;
margin: 0 10px;
}
</style>
子组件
<template>
<div class="child">
<h2>子组件</h2>
<GrandChild/>
</div>
</template>
<script setup lang="ts">
import GrandChild from './GrandChild.vue';
</script>
<style scoped>
.child {
background-color: rgb(76, 209, 76);
padding: 10px;
box-shadow: 0 0 10px black;
border-radius: 10px;
margin-top: 20px;
}
</style>
孙组件
<template>
<div class="GrandChild">
<h3>孙子组件</h3>
<h4>银子:{{ money }}</h4>
<h4>车子:一辆{{ car.brand }}, 价值{{car.price }}万元</h4>
<button @click="updateMoney(6)">花爷爷的钱</button>
</div>
</template>
<script setup lang="ts">
import { inject } from 'vue';
let {money,updateMoney} = inject('moneyContext',{money:0,updateMoney:(_x:number)=>{}})
let car = inject('car',{brand:'未知',price:'0'})
</script>
<style scoped>
.GrandChild {
background-color: rgb(62, 142, 222);
padding: 10px;
box-shadow: 0 0 10px black;
border-radius: 10px;
margin-top: 20px;
}
</style>
八、pinia
详见另一篇文章
九、插槽
默认插槽
父组件中:
<Category title="今日热门游戏">
<ul>
<li v-for="g in games" :key="g.id">{{ g.name }}</li>
</ul>
</Category>
子组件中:
<template>
<div class="item">
<h3>{{ title }}</h3>
<!-- 默认插槽 -->
<slot></slot>
</div>
</template>
具名插槽
父组件中:
<Category title="今日热门游戏">
<template v-slot:s1>
<ul>
<li v-for="g in games" :key="g.id">{{ g.name }}</li>
</ul>
</template>
<template #s2>
<a href="">更多</a>
</template>
</Category>
子组件中:
<template>
<div class="item">
<h3>{{ title }}</h3>
<slot name="s1"></slot>
<slot name="s2"></slot>
</div>
</template>
作用域插槽
- 理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(新闻数据在News组件中,但使用数据所遍历出来的结构由App组件决定)
- 具体编码:
父组件中:
<Game v-slot="params">
<!-- <Game v-slot:default="params"> -->
<!-- <Game #default="params"> -->
<ul>
<li v-for="g in params.games" :key="g.id">{{ g.name }}</li>
</ul>
</Game>
子组件中:
<template>
<div class="category">
<h2>今日游戏榜单</h2>
<slot :games="games" a="哈哈"></slot>
</div>
</template>
<script setup lang="ts" name="Category">
import {reactive} from 'vue'
let games = reactive([
{id:'asgdytsa01',name:'英雄联盟'},
{id:'asgdytsa02',name:'王者荣耀'},
{id:'asgdytsa03',name:'红色警戒'},
{id:'asgdytsa04',name:'斗罗大陆'}
])
</script>
总结
所有代码已上传Gitee
https://gitee.com/z8023y/vue3--component-communication.git