vue3 组件通信的常用方式
方式一 props
props
是使用频率最高的一种通信方式,常用与 :父 ↔ 子。
- 若 父传子:属性值是非函数。
- 若 子传父:属性值是函数。
父组件:
<template>
<div class="father">
<h3>父组件,</h3>
<h4>我的车:{{ car }}</h4>
<h4>儿子给的玩具:{{ toy }}</h4>
<Child :car="car" :getToy="getToy"/>
</div>
</template>
<script setup lang="ts" name="Father">
import Child from './Child.vue'
import { ref } from "vue";
// 数据
const car = ref('奔驰')
const toy = ref()
// 方法
function getToy(value:string){
toy.value = value
}
</script>
子组件:
<template>
<div class="child">
<h3>子组件</h3>
<h4>我的玩具:{{ toy }}</h4>
<h4>父给我的车:{{ car }}</h4>
<button @click="getToy(toy)">玩具给父亲</button>
</div>
</template>
<script setup lang="ts" name="Child">
import { ref } from "vue";
const toy = ref('奥特曼')
//接受父组件传来的值,子组件可以用父组件传来的函数进行与父组件的通信
defineProps(['car','getToy'])
</script>
方式2 自定义事件
-
自定义事件常用于:子 => 父
-
父组件可以通过
v-on
(缩写为@
) 来监听事件 -
子组件使用
$emit
方法触发自定义事件 -
例如:
<!-- 子组件通过$emit 触发事件 --> <button @click="$emit('someEvent')">click me</button>
<!-- 父组件通过@来监听事件 --> <MyComponent @some-event="callback" /> <!-- 注意这里我们触发了一个以 camelCase 形式命名的事件,但在父组件中可以使用 kebab-case 形式来监听。-->
-
子组件在触发事件时可以携带参数
例如:
<button @click="$emit('increaseBy', 1)"> Increase by 1 </button> <!-- 所有传入 $emit() 的额外参数都会被直接传向监听器。举例来说,$emit('foo', 1, 2, 3) 触发后,监听器函数将会收到这三个参数值。-->
然后我们在父组件中监听事件,需要接收事件附带的所有参数。
<MyButton @increase-by="increaseCount" /> function increaseCount(n) { count.value += n }
-
除了上面直接使用$emit方法触发,组件也可以显式地通过
defineEmits()
宏来声明它要触发的事件:<!-- 子组件中定义两个要触发的事件 --> <script setup> defineEmits(['inFocus', 'submit']) </script>
注意:在
<template>
中使用的$emit
方法不能在组件的<script setup>
部分中使用,但defineEmits()
会返回一个相同作用的函数供我们使用:<!-- 当点击了按钮,就相当于触发了submit事件,父组件监听到submit事件被触发,则执行相应的操作 --> <button @click="buttonClick"> <script setup> const emit = defineEmits(['inFocus', 'submit']) function buttonClick() { emit('submit') } </script>
再次注意:
defineEmits()
宏不能在子函数中使用。如上所示,它必须直接放置在<script setup>
的顶级作用域下。
方式3 mitt
概述:与消息订阅与发布(pubsub
)功能类似,可以实现任意组件间通信。
-
安装
mitt
使用npm i mitt
-
新建文件:
src\utils\emitter.ts
// 引入mitt import mitt from "mitt"; // 创建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
-
那么在任意两个组件中就可以导入这个
emitter
工具,然后通过emiiter.on()
和emitter.emit()
这两个api
进行监听和触发事件。同时一般需要在销毁前解绑事件。 -
接收数据的组件中:绑定事件、同时在销毁前解绑事件:
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) }
方式4 v-model
-
概述:实现 父↔子 之间相互通信。
-
v-model
的本质<!-- 使用v-model指令 --> <input type="text" v-model="userName"> <!-- v-model的本质是下面这行代码 --> <input type="text" :value="userName" @input="userName =$event.target.value" >
即:在父组件中,使用子组件时
<!-- 组件标签上v-model的本质 --> <!-- 在父组件中向子组件传一个modelValue的值 --> <!-- 同时监听子组件触发的事件 --> <AtguiguInput :modelValue="userName" @update:model-value="userName = $event"/>
子组件中:
<template> <div class="box"> <!--将接收的value值赋给input元素的value属性,目的是:为了呈现数据 --> <!--给input元素绑定原生input事件,触发input事件时,进而触发update:model-value事件--> <input type="text" :value="modelValue" @input="emit('update:model-value',$event.target.value)" > </div> </template> <script setup lang="ts" name="AtguiguInput"> // 接收props defineProps(['modelValue']) // 声明事件 const emit = defineEmits(['update:model-value']) </script>
-
v-model
在vue3.4 之后的用法,推荐的实现方式是使用defineModel()
宏:<!-- Child.vue --> <script setup> const model = defineModel() function update() { model.value++ } </script> <template> <div>parent bound v-model is: {{ model }}</div> </template>
<!-- Parent.vue --> <Child v-model="count" />
-
多个v-model 的绑定
<UserName v-model:first-name="first" v-model:last-name="last" />
<script setup> const firstName = defineModel('firstName') const lastName = defineModel('lastName') </script> <template> <input type="text" v-model="firstName" /> <input type="text" v-model="lastName" /> </template>
方式5 $attrs
概述:$attrs
用于实现当前组件的父组件,向当前组件的子组件通信(祖→孙)。
具体说明:$attrs
是一个对象,包含所有父组件传入的标签属性。
注意:
$attrs
会自动排除props
中声明的属性(可以认为声明过的props
被子组件自己“消费”了)
例如:
<!-- 父组件-->
<template>
<div class="father">
<h3>父组件</h3>
<!-- 向孩子 传入多个标签属性 -->
<Child :a="a" :b="b" :c="c" :d="d" v-bind="{x:100,y:200}" :updateA="updateA"/>
</div>
</template>
<script setup lang="ts" name="Father">
import Child from './Child.vue'
import { ref } from "vue";
let a = ref(1)
let b = ref(2)
let c = ref(3)
let d = ref(4)
function updateA(value){
a.value = value
}
</script>
<template>
<div class="child">
<h3>子组件</h3>
<GrandChild v-bind="$attrs"/>
</div>
</template>
<script setup lang="ts" name="Child">
import GrandChild from './GrandChild.vue'
// 子组件只用props接收了a标签,其他的标签属性还都在$attrs中,继续传向孙子组件
defineProps('a')
</script>
<template>
<div class="grand-child">
<h3>孙组件</h3>
<h4>a:{{ a }}</h4>
<h4>b:{{ b }}</h4>
<h4>c:{{ c }}</h4>
<h4>d:{{ d }}</h4>
<h4>x:{{ x }}</h4>
<h4>y:{{ y }}</h4>
<button @click="updateA(666)">点我更新A</button>
</div>
</template>
<script setup lang="ts" name="GrandChild">
//孙子组件接收了剩下的标签属性实现了通信
defineProps(['a','b','c','d','x','y','updateA'])
</script>
方式6 provide,injecct
-
概述:实现祖孙组件直接通信
-
具体使用:
- 在祖先组件中通过
provide
配置向后代组件提供数据 - 在后代组件中通过
inject
配置来声明接收数据
- 在祖先组件中通过
-
具体编码:
<template> <div class="father"> <h3>父组件</h3> <h4>资产:{{ money }}</h4> <h4>汽车:{{ car }}</h4> <button @click="money += 1">资产+1</button> <button @click="car.price += 1">汽车价格+1</button> <Child/> </div> </template> <script setup lang="ts" name="Father"> import Child from './Child.vue' import { ref,reactive,provide } from "vue"; // 数据 let money = ref(100) let car = reactive({ brand:'奔驰', price:100 }) // 用于更新money的方法 function updateMoney(value:number){ money.value += value } // 提供数据 provide('moneyContext',{money,updateMoney}) provide('car',car) </script>
注意:这种好处明显好于
$attrs
因为没有中间人,子组件中不用编写任何东西,是不受到任何打扰的<template> <div class="grand-child"> <h3>我是孙组件</h3> <h4>资产:{{ money }}</h4> <h4>汽车:{{ car }}</h4> <button @click="updateMoney(6)">点我</button> </div> </template> <script setup lang="ts" name="GrandChild"> import { inject } from 'vue'; // 注入数据 let {money,updateMoney} = inject('moneyContext',{money:0,updateMoney:(x:number)=>{}}) let car = inject('car') </script>