方式一、以父组件为中间桥梁,借助 emit 和 props
1. 整体思路
假设 A 和 B 是兄弟组件,A 要传值给 B
① 在 父组件 中使用 A 和 B 组件标签
② 在 A 标签身上绑定一个 自定义事件 on-click,并且赋值一个回调函数,这个函数是定义在父组件中的
③ 在 B 标签身上绑定 自定义属性,并且这些属性在 父组件 中要有定义(且是响应式的)
④ 在 A 组件中使用 emit 触发父组件给自己绑定的 自定义事件,并传递参数
⑤ 在 父组件 中接收这些参数,并赋值给第 ③ 步中的自定义属性
⑥ 在 B 组件中使用 props 接收父组件给自己绑定的 自定义属性
2. 具体实现
父组件中
// App.vue
<template>
<bro-a @on-click="fn"></bro-a>
<bro-b :Flag="Flag"></bro-b>
</template>
<script setup lang="ts">
import { Ref, ref } from 'vue';
import BroA from './components/BroA.vue';
import BroB from './components/BroB.vue';
let Flag = ref(true)
const fn = (flag: boolean) => {
Flag.value = flag
}
</script>
<style scoped>
</style>
A 组件中
// A.vue
<template>
<div>
兄弟组件 A
<br>
<button @click="changeFlag">修改 Flag</button>
</div>
</template>
<script setup lang="ts">import { ref } from 'vue';
const emit = defineEmits(['on-click'])
let Flag = true
const changeFlag = () => {
Flag = !Flag
emit('on-click', Flag)
}
</script>
<style scoped>
div{
width: 200px;
height: 200px;
background-color: bisque;
font-size: 30px;
}
</style>
B 组件中
// B.vue
<template>
<div>
兄弟组件 B
<br>
{{ Flag }}
</div>
</template>
<script setup lang="ts">
defineProps<{Flag:boolean}>()
</script>
<style scoped>
div {
width: 200px;
height: 200px;
background-color: aqua;
font-size: 30px;
}
</style>
呈现效果是,A 组件中修改 Flag 的值,B 组件中也会响应
方式二、事件总线 Bus(发布订阅模式)
1. 实现思路
① 建立一个 事件订阅发布 中心,主要作用就是收集所有 订阅,以及 发布 事件
② 在其它组件中,如果想 订阅(on) 和 发布(emit) 事件,需要调用 事件订阅发布中心 的 API 来实现
③ 需要注意的是,同一个事件(事件名称相同)可以被多次 订阅,当 发布 时,需要同时触发所有的 事件回调
2. 简单实现和使用
实现
全局事件订阅发布中心
// Bus.ts
type BusClass = {
emit: (name: string) => void,
on: (name: string, cb: Function) => void
}
type ParamsKey = string | number | symbol
type List = {
// 因为函数可以多次订阅,所以要 key-array 的形式,array 就是一个函数数组
// 一旦 key 被发布,那么这个 key 对应的数组里面的所有函数将被一起触发
[key: ParamsKey]: Array<Function>
}
class Bus implements BusClass{
// 定义一个对象,key 是事件名称,value 是事件回调数组
list: List
// 类构造函数初始化
constructor() {
this.list = {}
}
// 事件订阅函数
on(name: string, cb: Function) {
// 将 list 里面的 同名函数(形成一个函数数组) 都取出来
let fnArr: Array<Function> = this.list[name] || []
// 将 订阅的函数回调 放入这个数组
fnArr.push(cb)
this.list[name] = fnArr
}
// 事件发布函数
emit(name: string, ...args: Array<any>) {
// name(key) 被发布,其对应的所有函数都被触发
let fnArr: Array<Function> = this.list[name]
fnArr.forEach(fn => {
fn.apply(this, args)
})
}
}
// 暴露一个事件总线实例
export default new Bus()
使用
B 组件订阅事件
<template>
<div>
兄弟组件 B
<br>
{{ Flag }}
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import Bus from '../utils/Bus'
let Flag = ref(true)
const fn = (flag: boolean) => {
Flag.value = flag
}
// 定义自定义事件 'on-click',当被触发时,会调用 fn 回调函数,接收参数值
Bus.on('on-click', fn)
</script>
<style scoped>
div {
width: 200px;
height: 200px;
background-color: aqua;
font-size: 30px;
}
</style>
A 组件发布事件
<template>
<div>
兄弟组件 A
<br>
<button @click="changeFlag">修改 Flag</button>
</div>
</template>
<script setup lang="ts">
import Bus from '../utils/Bus';
let Flag = true
const changeFlag = () => {
Flag = !Flag
// 发布自定义事件 'on-click',并且传参,所有 订阅了该事件的地方 都将触发 回调函数
Bus.emit('on-click', Flag)
}
</script>
<style scoped>
div {
width: 200px;
height: 200px;
background-color: bisque;
font-size: 30px;
}
</style>
可以将 A 和 B 放入 App 中,验证结果
<template>
<bus-a></bus-a>
<bus-b></bus-b>
</template>
<script setup lang="ts">
import BusA from './components/BusA.vue';
import BusB from './components/BusB.vue';
</script>
<style scoped>
</style>
可以看到,A 触发事件后,能调用 B 的回调,从而传值给 B
扩展
虽然 vue3 移除了事件总线,但可以借助第三方包,使用事件总线
1. 安装 npm i mitt -S
2. main.ts 初始化
全局总线,vue 入口文件中挂载全局属性
import { createApp } from 'vue'
import App from './App.vue'
// 1. 引入 mitt,是一个函数
import mitt from 'mitt'
// 2. 调用 mitt
const Mit = mitt()
// 3. TypeScript注册
// 由于必须要拓展 ComponentCustomProperties 类型才能获得类型提示
declare module "vue" {
export interface ComponentCustomProperties {
$Bus: typeof Mit
}
}
const app = createApp(App)
// 4. vue3 挂载全局 API
app.config.globalProperties.$Bus = Mit
app.mount('#app')
3. 使用
on 订阅,emit 发布,off 取消订阅,all.clear 取消所有订阅
A 组件派发(emit)
<template>
<div>
<h1>我是 A </h1>
<button @click="emit1">emit1</button>
<button @click="emit2">emit2</button>
</div>
</template>
<script setup lang="ts">
import { getCurrentInstance } from 'vue'
// 获取全局的 vue 实例
const instance = getCurrentInstance();
const emit1 = () => {
// 调用实例身上挂载的 $Bus
instance?.proxy?.$Bus.emit('on-num', 100)
}
const emit2 = () => {
instance?.proxy?.$Bus.emit('*****', 500)
}
</script>
B 组件监听(on)
<template>
<div>
<h1>我是 B </h1>
</div>
</template>
<script setup lang="ts">
import { getCurrentInstance } from 'vue'
const instance = getCurrentInstance()
instance?.proxy?.$Bus.on('on-num', (num) => {
console.log(num, 'B')
})
</script>
监听所有事件(on("*"))
instance?.proxy?.$Bus.on("*", (type, num) => { // 第一个参数是 自定义事件的名称
console.log(type, num, 'B')
})
移除监听事件(off)
const Fn = (num: any) => {
console.log(num, 'B')
}
instance?.proxy?.$Bus.on('on-num', Fn) // listen
instance?.proxy?.$Bus.off('on-num', Fn) // unListen
清空所有监听(clear)
instance?.proxy?.$Bus.all.clear()