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)
})
}
}
}