【Vue3】20-兄弟组件传值

6,077 阅读3分钟

方式一、以父组件为中间桥梁,借助 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()