Vue 3 数据总线

530 阅读2分钟

Vue 3 中的数据总线:使用 mitt 实现组件通信

在 Vue 开发中,组件通信 是一个非常重要的主题。对于父子组件之间的通信,我们可以通过 props/emit 来实现。然而,在 非父子组件 或 兄弟组件 之间进行通信时,传统的 props/emit 就显得力不从心了。这时,事件总线(Event Bus) 就派上了用场。

在 Vue 2 中,我们可以通过 new Vue() 创建一个事件总线实例,并使用 $emit$on$off 来实现事件的发布与订阅。然而,在 Vue 3 中,这些 API 被移除了。幸运的是,我们可以借助一个轻量级的开源库 —— mitt 来实现类似的功能。

什么是 mitt

mitt 是一个极简的JavaScript事件发布/订阅库,体积仅有200字节左右,但却功能强大。它可以在任何 JavaScript 环境中使用,包括 Vue、React 甚至原生 JS。mitt 的核心功能非常简单:

  • 发布事件:通过 emit 方法触发事件。

  • 订阅事件:通过 on 方法监听事件。

  • 取消订阅:通过 off 方法移除事件监听器。

如何在 Vue 3 中使用 mitt

1、安装mitt

首先,我们需要通过 npm 安装 mitt

npm i mitt

2、创建事件总线

接下来,我们创建一个事件总线实例

import mitt from "mitt";

const emitter = mitt();
export default emitter;

3、在组件中订阅事件

<template>
  <div>
    <div>Bar: {{ date }}</div>
  </div>
</template>
<script setup lang="ts">
import { onBeforeMount, onMounted, ref } from 'vue';
import emitter from './emitter';

const date = ref()
onMounted(() => {
  emitter.on('bar', (data) => {
    date.value = data
  })
})

onBeforeMount(() => {
  emitter.off('bar')
})
</script>

4、在组件中发布事件

// Foo.vue
<template>
  <div>
    <span>Foo: </span>
    <button @click="click">触发事件</button>
  </div>
</template>
<script setup lang="ts">
import emitter from './emitter';

function click() {
  emitter.emit('bar', new Date().toLocaleString());
}
</script>

mitt 实现原理

miit的原理其实非常简单,主要依赖于一个 Map 对象来存储事件类型及其对应的处理函数。以下是mitt的简化实现:

function mitt() {
    const all = new Map();
    return {
        // 订阅事件
        on(type, handler) {
            const handlers = all.get(type)
            if (handlers) {
                handlers.push(handler)
            } else {
                all.set(type, [handler])
            }
        },
        // 取消订阅
        off(type, handler) {
            const handlers = all.get(type)
            if (!handlers) {
                return
            }
            if (handler) {
                const index = handlers.indexOf(handler)
                if (index > -1) {
                    handlers.splice(index, 1)
                }
            } else {
                all.set(type, [])
            }
        },
        // 发布事件
        emit(type, ...args) {
            const handlers = all.get(type);
            handlers?.slice().map(handler => {
                handler(...args)
            })
        }
    }
}