借助父组件传参

交替使用emit和prop,以父组件为桥梁进行兄弟间的传参。

App.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div>
<!-- 这两个子组件是平级的 -->
<A @on-click="getFlag"></A>
<B :flag="flag"></B>
</div>
</template>

<script setup lang="ts">
import { ref } from "vue"
import A from "@/components/A.vue"
import B from "@/components/B.vue"

let flag = ref(false)
// 父组件将接收到的A组件的传值传给B组件
const getFlag = (param: boolean)=> {
flag.value = param
}
</script>

A.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div class="a">
<h1>A组件</h1>
<button @click="emitB">派发一个事件</button>
</div>
</template>

<script setup lang="ts">
const emit = defineEmits(["on-click"])
let flag = false
// 通过自定义事件给父组件App.vue传递数据
const emitB = ()=> {
flag = !flag
emit("on-click", flag)
}
</script>

<style scoped lang="scss">
.a {
width: 200px;
height: 200px;
background-color: cyan;
}
</style>

B.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div class="b">
<h1>B组件</h1>
{{ flag }}
</div>
</template>

<script setup lang="ts">
defineProps<{
flag: boolean
}>()
</script>

<style scoped lang="scss">
.b {
width: 200px;
height: 200px;
background-color: orange;
}
</style>

但是这样太麻烦了,每次都要在父组件这里处理逻辑。

借助Event Bus传参

简单实现一个发布者-订阅者模式。

bus.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
type BusClass = {
// emit的其他参数会传给on的callback函数
emit:(name:string)=>void
on:(name:string,callback:Function)=>void
}

type ParamsKey = string | number | symbol

type List = {
[key:ParamsKey]:Array<Function>
}

class Bus implements BusClass {
// 调度中心
list:List

constructor() {
this.list = {}
}

emit(name:string,...args:Array<any>) {
// 取得回调函数集合
const eventName: Array<Function> = this.list[name]
eventName.forEach(fn=> {
fn.apply(this,args)
})
}
on(name:string,callback:Function) {
// 获取事件注册的回调函数数组,如果没有数组就新建一个
const fn:Array<Function> = this.list[name] || []
fn.push(callback)
this.list[name] = fn
}
}

export default new Bus()

App.vue:

1
2
3
4
5
6
7
8
9
10
11
<template>
<div>
<A></A>
<B></B>
</div>
</template>

<script setup lang="ts">
import A from "@/components/A.vue"
import B from "@/components/B.vue"
</script>

A.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div class="a">
<h1>A组件</h1>
<button @click="emitB">派发一个事件</button>
</div>
</template>

<script setup lang="ts">
import Bus from "../bus"
let flag = false

const emitB = ()=> {
flag = !flag
Bus.emit("on-click",flag)
}
</script>

<style scoped lang="scss">
.a {
width: 200px;
height: 200px;
background-color: cyan;
}
</style>

B.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div class="b">
<h1>B组件</h1>
{{ flag }}
</div>
</template>

<script setup lang="ts">
import { ref } from "vue"
import Bus from "../bus"

let flag = ref(false)
Bus.on("on-click", (f:boolean)=>{
flag.value = f
})
</script>

<style scoped lang="scss">
.b {
width: 200px;
height: 200px;
background-color: orange;
}
</style>

Mitt

在Vue2中常用全局事件总线,但Vue3中$on$off$once实例方法都已被删除,组件实例不再实现事件触发接口,因此就不能像Vue2一样使用全局事件总线了。

作为替代,可以使用Mitt库(就是发布订阅模式)

在main.ts中将Mitt挂载到全局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 其他的省略
import mitt from "mitt"

const Mit = mitt()

// typescript注册
// 必须要拓展ComponentCustomProperties类型才能获得类型提示
declare module "vue" {
export interface ComponentCustomProperties {
$Bus: typeof Mit
}
}

// Vue3 挂载全局API
app.config.globalProperties.$Bus = Mit

App.vue:

1
2
3
4
5
6
7
8
9
10
11
<template>
<div>
<A></A>
<B></B>
</div>
</template>

<script setup lang="ts">
import A from "@/components/A.vue"
import B from "@/components/B.vue"
</script>

A.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<template>
<div class="a">
<h1>A组件</h1>
<button @click="emitB">派发一个事件</button>
</div>
</template>

<script setup lang="ts">
import { getCurrentInstance } from 'vue'

// 获取当前组件的实例
const instance = getCurrentInstance()

let flag = false

const emitB = ()=> {
flag = !flag
instance?.proxy?.$Bus.emit("on-click",flag)
instance?.proxy?.$Bus.emit("on-str","strstr")
}
</script>

<style scoped lang="scss">
.a {
width: 200px;
height: 200px;
background-color: cyan;
}
</style>

B.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<template>
<div class="b">
<h1>B组件</h1>
<button @click="cancelFlag">取消监听flag</button>
<button @click="cancelAll">取消所有事件</button>
{{ flag }}
</div>
</template>

<script setup lang="ts">
import { ref, getCurrentInstance } from "vue"

const instance = getCurrentInstance()

const cb = (f: unknown)=>{
flag.value = f as boolean
}

let flag = ref(false)
// 监听on-click flag变化的事件
instance?.proxy?.$Bus.on("on-click", cb)

// *表示监听所有的事件触发
instance?.proxy?.$Bus.on("*", (type, arg)=>{
// type:事件名称 其他参数:事件传参
console.log(type,arg)
})

// 取消on-click flag的事件
const cancelFlag = ()=> {
// 参数1:取消的事件名 参数2:取消的回调函数
instance?.proxy?.$Bus.off("on-click", cb)
}

// 取消所有事件
const cancelAll = ()=> {
instance?.proxy?.$Bus.all.clear()
}
</script>

<style scoped lang="scss">
.b {
width: 200px;
height: 200px;
background-color: orange;
}
</style>

也可以通过引入mitt使用。