vue3组件通信常用几种方法
1、props
父传子:通过在父组件中的子组件身上添加属、传递参数;在子组件中通过defineProps接收参数。
<--!父组件-->
<template>
<div style="background-color: aqua;">
01_props-父
<div>
子给的姓名:{{ childName }}
</div>
<child :name="name"></child>
</div>
</template>
<script setup lang="ts">
import child from './child.vue';
import { ref } from 'vue';
const name = ref('孩子爸')
const childName = ref('')
</script>
<--!子组件-->
<template>
<div style="background-color: bisque;margin-left: 10px; width: 80%; height: 80%;">
子组件
<div>
父的名:{{ name }}
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
defineProps(['name'])
</script>
子传父:首先需要在父组件中定义接收的方法,通过子组件defineProps接收父组件方法,调用父组件的方法实现传参。
<--!父组件-->
<template>
<div style="background-color: aqua;">
01_props-父
<div>
子给的姓名:{{ childName }}
</div>
<child :sendName="getName"></child>
</div>
</template>
<script setup lang="ts">
import child from './child.vue';
import { ref } from 'vue';
const childName = ref('')
//定于接收方法
function getName(value: any) {
childName.value = value
}
</script>
<style lang='scss' scoped></style>
<--!子组件-->
<template>
<div style="background-color: bisque;margin-left: 10px; width: 80%; height: 80%;">
子组件
<--! 调用父组件中的方法并传参 -->
<button @click="sendName(childName)">
发送姓名
</button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const childName = ref('孩子')
defineProps(['sendName'])
</script>
2、custom-event(子传父)通常用于子传父
<!--父组件-->
<template>
<div style="background-color: aqua;">
02_custom-event
<div>父组件</div>
<div>子组件的值:{{ childName}}</div>
<!--在父组件中,给子组件绑定自定义事件:-->
<child @send-child="getChild"></child>
</div>
</template>
<script setup lang="ts">
import child from './child.vue';
import { ref } from 'vue';
const childName = ref('')
function getChild(value:any){
childName.value = value
}
</script>
<!--子组件-->
1、直接传值
<template>
<div style="background-color: bisque;margin-left: 10px; width: 80%; height: 80%;">
子组件
<div>
子组件:{{ childName }}
</div>
<!--子组件中,触发事件: -->
<buttonc @click="emit('send-child',childName)">传值</buttonc>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const childName = ref('孩子')
//通过defindEmits来接收父组件给子组件绑定的自定义事件
const emit = defineEmits(['send-child'])
</script>
2、经过函数处理后传值
<template>
<div style="background-color: bisque;margin-left: 10px; width: 80%; height: 80%;">
子组件
<div>
子组件:{{ childName }}
</div>
<!--子组件中,触发事件: -->
<button @click="send">传值</button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const childName = ref('孩子')
const emit = defineEmits(['send-child'])
function send(){
childName.value = '孩子2'
emit('send-child',childName.value)
}
</script>
*他们的本质是没有改变的,传值的方式都一样。
*只不过第二种改变了形式,在传参前经过了一些处理,在实际的开发中会更方便
3、mitt 可实现父组件中多个子组件间相互传值
3.1安装
npm install mitt
3.2在src目录下新建utils文件夹,在utils文件夹李新建emitter.ts文件
//引入mitt
import mitt from 'mitt'
//调用mitt得到emitter,emitter能:绑定事件、触发事件
const emitter = mitt()
//基本用法
// 绑定事件
emitter.on('abc',(value)=>{
console.log('abc事件被触发',value)
})
emitter.on('xyz',(value)=>{
console.log('xyz事件被触发',value)
})
setInterval(() => {
// 触发事件
emitter.emit('abc',666)
emitter.emit('xyz',777)
}, 1000);
setTimeout(() => {
// 清理事件
emitter.all.clear()
}, 3000);
*/
//对外暴露mitt
export default emitter
3.3使用
<!--父组件-->
<template>
<div style="background-color: aqua;">
03_mitt
<div>父组件</div>
<div>
<child></child>
<child2></child2>
</div>
</div>
</template>
<script setup lang="ts">
import child from './child.vue';
import child2 from './child2.vue';
</script>
<!--子组件-01-->
<template>
<div style="background-color: bisque;margin-left: 10px; width: 80%; height: 80%;">
子组件1
<div>
子组件1:{{ childName }}
</div>
<!--子组件中,触发事件: -->
<button @click="send">传值</button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import emitter from '@/utils/emitter'
const childName = ref('组件1')
//触发mitt里为组件2绑定的函数,并将自己的值传给组件2
//触发函数时需要传入参数
function send() {
emitter.emit('send-msg', childName.value)
}
//在mitt里为组件1绑定函数,定义接受参数的函数
emitter.on('send-msg2', (value:any) => {
console.log("组件1,来自组件2的值:",value);
})
</script>
<!--子组件-02-->
<template>
<div style="background-color: rgb(225, 134, 23);margin-left: 10px; width: 80%; height: 80%;">
子组件2
<div>
子组件2:{{ childName }}
</div>
<!--子组件中,触发事件: -->
<button @click="send">传值</button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import emitter from '@/utils/emitter'
const childName = ref('组件2')
//在mitt里为组件2绑定函数,定义接受参数的函数
emitter.on('send-msg', (value:any) => {
console.log("组件2,来自组件1的值:",value);
})
//触发mitt里为组件1绑定的函数,并将组件的值传给组件1
//触发函数时需要传入参数
function send(){
emitter.emit('send-msg2',childName.value)
}
</script>
4、v-model实现父子组件通信(自定义input组件)
首先了解一下v-model的本质
<input
type="text"
:value="userName"
@input="userName =(<HTMLInputElement>$event.target).value"
>
如何使用
<!--父组件-->
<template>
<div style="background-color: aqua;">
04_v-model(父组件)
<div>
<!-- 组件标签上使用v-model指令 -->
<DemoInput v-model="userName"></DemoInput>
<!-- 组件标签上v-model的本质 -->
<!-- <DemoInput :modelValue="userName" @update:modelValue="userName = $event" /> -->
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import DemoInput from './demoInput.vue'
const userName = ref("12312312")
</script>
<!--子组件-->
<template>
<div>
<!-- v-model的本质是下面这行代码 -->
<!-- <input
type="text"
:value="userName"
@input="userName =(<HTMLInputElement>$event.target).value"
> -->
<!--将接收的value值赋给input元素的value属性,目的是:为了呈现数据 -->
<!--给input元素绑定原生input事件,触发input事件时,进而触发update:model-value事件-->
<input type="text" :value="modelValue" @input="emit('update:modelValue', (<HTMLInputElement>$event.target).value)">
</div>
</template>
<script setup lang="ts">
// 接收props
defineProps(['modelValue'])
// 声明事件
const emit = defineEmits(['update:modelValue'])
</script>
多个v-model(如果value
可以更换,那么就可以在组件标签上多次使用v-model
)
<!--父组件-->
<template>
<div style="background-color: aqua;">
04_v-model(父组件)
<div>
<!-- 组件标签上使用v-model指令 -->
<!--<DemoInput v-model="userName"></DemoInput>-->
<!-- 修改modelvaue -->
<DemoInput v-model:username="userName" v-model:password="passWord"></DemoInput>
<!-- 组件标签上v-model的本质 -->
<!-- <DemoInput :modelValue="userName" @update:modelValue="userName = $event" /> -->
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import DemoInput from './demoInput.vue'
const userName = ref("12312312")
</script>
<!--子组件-->
<template>
<div>
<input type="text" :value="username" @input="emit('update:username', (<HTMLInputElement>$event.target).value)">
<input type="text" :value="password" @input="emit('update:password', (<HTMLInputElement>$event.target).value)">
</div>
</template>
<script setup lang="ts">
// 接收props
defineProps(['username', 'password'])
// 声明事件
const emit = defineEmits(['update:username', 'update:password'])
</script>
5、attrs
5.1. 概述:
$attrs
用于实现当前组件的父组件,向当前组件的子组件通信(祖→孙)。
5.2. 具体说明:
$attrs
是一个对象,包含所有父组件传入的标签属性。
注意:
$attrs
会自动排除props
中声明的属性(可以认为声明过的props
被子组件自己“消费”了)
template>
<div style="background-color: blanchedalmond;">
05_$attrs
<h4>a:{{ a }}</h4>
<h4>b:{{ b }}</h4>
<h4>c:{{ c }}</h4>
<child :a="a" :b="b" :c="c" :updateA="updatedA"></child>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import child from "./child.vue";
const a = ref(1)
const b = ref(2)
const c = ref(3)
function updatedA (value:number) {
a.value +=value
}
</script>
<!--子组件-->
<template>
<div style="background-color: rgb(238, 131, 1);margin: 10px;">
子组件
<grandChild v-bind="$attrs"></grandChild>
</div>
</template>
<script setup lang="ts">
import grandChild from './grandChild.vue';
</script>
<template>
<div style="background-color: aqua;margin: 10px;">
孙组件
<h4>a:{{ a }}</h4>
<h4>b:{{ b }}</h4>
<h4>c:{{ c }}</h4>
<button @click="updateA(1)">点我给爷爷a+1</button>
</div>
</template>
<script setup lang="ts">
defineProps(['a','b','c','updateA'])
</script>
6、 r e f s 、 refs、 refs、parent
$refs
用于 :父→子。$parent
用于:子→父。
想要组件拿到数据,必须要使用
defineExpose
将数据交出
原理如下:
属性 | 说明 |
---|---|
$refs | 值为对象,包含所有被ref 属性标识的DOM 元素或组件实例。 |
$parent | 值为对象,当前组件的父组件实例对象。 |
<!--父组件-->
<template>
<div style="background-color: blanchedalmond;">
06_$ref-$parent
<h2>父组件</h2>
<h2>房子:{{ home }}</h2>
<button @click="addCar">给c1再买一辆车</button>
<button @click="addPhone">给c2再买一部手机</button>
<button @click="getALL($refs)">获取所有的子组件的实例对象</button>
<div style="display: flex;">
<child1 ref="c1" />
<child2 ref="c2"></child2>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import child1 from './child1.vue';
import child2 from './child2.vue';
const home = ref(4)
const c1 = ref()
const c2 = ref()
//方法
function addCar() {
c1.value.car += 1
console.log(c1.value);
}
function addPhone() {
c2.value.phone += 1
}
function getALL(refs:{[key:string]:any}) {
for (let key in refs) {
refs[key].money += 100
// console.log(key);
}
// console.log(ref);
}
//把数据交出去
defineExpose({home})
</script>
<!--组件1-->
<template>
<div style="background-color: aquamarine;margin-right: 20px;">
子组件1
<h4>车:{{ car }}</h4>
<h4>资产:{{money}}</h4>
<button @click="minHome($parent)">拿走父亲一套房</button>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
const car = ref(2)
const money = ref(100)
//子组件管理父组件里的数据
function minHome(parent:object){
console.log(parent);
parent.home -=1
}
//把数据交出去
defineExpose({car,money})
</script>
<!--子组件2-->
<template>
<div style="background-color: aquamarine;">
子组件2
<h4>手机:{{phone}}</h4>
<h4>资产:{{money}}</h4>
<button @click="addHome($parent)">给父亲买一套房</button>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
const phone = ref(3)
const money = ref(100)
//子组件管理父组件里的数据
function addHome(parent:object){
console.log(parent);
parent.home +=1
}
//把数据交出去
defineExpose({phone,money})
</script>
7、provide-inject
注意:子组件中不用编写任何东西,是不受到任何打扰的
- 在祖先组件中通过
provide
配置向后代组件提供数据 - 在后代组件中通过
inject
配置来声明接收数据
<!--父组件:父组件中,使用`provide`提供数据-->
<template>
<div style="background-color: blanchedalmond;">
07_provide-inject
<h3>父组件</h3>
<h4>资产{{ money}}w</h4>
<h4>汽车{{ car.brand }},价值{{car.price}}w</h4>
<child></child>
</div>
</template>
<script setup lang="ts">
import child from "./child.vue";
import { reactive, ref , provide} from "vue";
const money = ref(100)
const car =reactive({
brand:'奥迪',
price:100
})
function addMoney(){
money.value += 100
}
//向后代提供数据
provide('Money',{money,addMoney})
provide('Car',car)
</script>
<!--孙组件:孙组件中使用`inject`配置项接受数据。-->
<template>
<div style="background-color: aqua;margin: 10px;">
孙组件
<h4>资产{{ money}}w</h4>
<h4>汽车{{ car.brand }},价值{{car.price}}w</h4>
<button @click="addMoney(100)">给爷爷钱</button>
</div>
</template>
<script setup lang="ts">
import { inject } from 'vue';
let {money,addMoney} = inject('Money',{money:0,addMoney:(x:number)=>{}})
//{brand:'未知',price:0}帮助推断
let car = inject('Car',{brand:'未知',price:0})
</script>
8、pinia组件通信
9、【slot】插槽
9.1默认插槽
<!--父组件-->
<template>
<div style="background-color: aquamarine;">
09_slot
<h3>父组件</h3>
<div style="display: flex;justify-content: space-evenly;width: 100%;height: 80%;">
<Category title="热门游戏">
<div v-for="game in games" :key="game.id" style="background-color: yellowgreen;">
{{ game.name }}
</div>
</Category>
<Category title="美食分享">
<img style="width: 200px;" :src="imgUrl" alt="" srcset="">
</Category>
<Category title="影视推荐">
<video controls :src="videoUrl"></video>
</Category>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue';
import Category from './Category.vue';
let games = reactive([
{ id: 'asgdytsa01', name: '英雄联盟' },
{ id: 'asgdytsa02', name: '王者荣耀' },
{ id: 'asgdytsa03', name: '红色警戒' },
{ id: 'asgdytsa04', name: '斗罗大陆' }
])
let imgUrl =ref('https://z1.ax1x.com/2023/11/19/piNxLo4.jpg')
let videoUrl = ref('http://clips.vorwaerts-gmbh.de/bigbuckbunny.mp4')
</script>
<!--子组件-->
<template>
<div style="background-color: blanchedalmond;">
<!-- <h4>子组件</h4> -->
<h2>{{ title }}</h2>
<!-- 默认插槽 -->
<slot></slot>
</div>
</template>
<script setup lang="ts">
defineProps(['title'])
</script>
9.2具有名字的插槽
通过在父组件中引入的子组件身上添加
<template v-slot:s1>
或者<template #s2>
来定义父组件中的元素具体放在子组件中的那个位置
<!--父组件-->
<template>
<div style="background-color: aquamarine;">
09_slot
<h3>父组件</h3>
<div style="display: flex;justify-content: space-evenly;width: 100%;height: 80%;">
<Category title="热门游戏">
<template v-slot:s1>
<div v-for="game in games" :key="game.id" style="background-color: yellowgreen;">
{{ game.name }}
</div>
</template>
<!--一个组件中可以放置多个<template>标签-->
<template #s2>
<div>more</div>
</template>
</Category>
<Category title="美食分享">
<img style="width: 200px;" :src="imgUrl" alt="" srcset="">
</Category>
<Category title="影视推荐">
<video controls :src="videoUrl"></video>
</Category>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue';
import Category from './Category.vue';
let games = reactive([
{ id: 'asgdytsa01', name: '英雄联盟' },
{ id: 'asgdytsa02', name: '王者荣耀' },
{ id: 'asgdytsa03', name: '红色警戒' },
{ id: 'asgdytsa04', name: '斗罗大陆' }
])
let imgUrl = ref('https://z1.ax1x.com/2023/11/19/piNxLo4.jpg')
let videoUrl = ref('http://clips.vorwaerts-gmbh.de/bigbuckbunny.mp4')
</script>
<!--子组件-->
<template>
<div style="background-color: blanchedalmond;">
<!-- <h4>子组件</h4> -->
<h2>{{ title }}</h2>
<slot name="s1"></slot>
<slot name="s2"></slot>
</div>
</template>
<script setup lang="ts">
defineProps(['title'])
</script>
9.3作用域插槽
所有的数据和行为由
子组件
决定,但是数据呈现的方式由父组件
决定;也可以拥有具有名字的插槽
<!--父组件-->
<template>
<div style="background-color: aquamarine;">
09_slot
<h3>父组件</h3>
<div style="display: flex;justify-content: space-evenly;width: 100%;height: 80%;">
<game title="游戏列表">
<template v-slot:GAME="params">
<div v-for="g in params.games" :key="g.id">
{{ g.name }}
</div>
</template>
<!-- 默认插槽不能带有具名 -->
<!-- <template #default="params">
<div v-for="g in params.games" :key="g.id">
{{ g.name }}
</div>
</template> -->
</game>
</div>
</div>
</template>
<script setup lang="ts">
import game from './game.vue';
</script>
<!--子组件-->
<template>
<div class="category">
<h2>{{ title }}</h2>
<slot name="GAME" :games="games"></slot>
</div>
</template>
<script setup lang="ts" name="Category">
import { reactive } from 'vue'
defineProps(['title'])
let games = reactive([
{ id: 'asgdytsa01', name: '英雄联盟' },
{ id: 'asgdytsa02', name: '王者荣耀' },
{ id: 'asgdytsa03', name: '红色警戒' },
{ id: 'asgdytsa04', name: '斗罗大陆' }
])
</script>