这一节了解一下Vue3中的mitt,mitt是Vue3中最常用的轻量级事件总线库,本质是一个基于发布-订阅模式的独立EventEmitter,总结如下:
API:
emitter.on(event, handler):监听事件,注册回调函数,当事件触发时执行
emitter.emit(event, data):触发事件,发布事件并携带数据,通知所有监听者
emitter.off(event, handler):取消监听, 移除指定事件的指定回调,防止内存泄漏
emitter.all.clear():清空所有监听,一次性移除所有事件的所有回调
场景
1 兄弟组件/非父子组件通信:两个无直接关系的组件需要传递消息。
2 全局通知/消息提示:任意组件触发全局Toast、Notification等。
3 跨多层组件传递消息:避免props逐层透传。
栗子:
TypeScript
src/utils/eventBus.ts
import mitt from 'mitt'
const emitter = mitt()
export default emitter
TypeScript
发送方BrotherA.vue
<template>
<div style="border:1px solid #ccc;padding:10px;margin:8px;">
<h3>兄弟A(发送方)</h3>
<button @click="sendMsg">给兄弟B发送消息</button>
</div>
</template>
<script setup lang="ts">
import emitter from '@/utils/eventBus'
const sendMsg = () => {
emitter.emit('send-to-b', {
name: '兄弟A',
content: '你好,我是A组件消息'
})
}
</script>
TypeScript
接收方 BrotherB.vue
<template>
<div style="border:1px solid #ccc;padding:10px;margin:8px;">
<h3>兄弟B(接收方)</h3>
<p>收到消息:{{msg}}</p>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue'
import emitter from '@/utils/eventBus'
const msg = ref('')
onMounted(() => {
emitter.on('send-to-b', (data) => {
msg.value = `${data.name}:${data.content}`
})
})
onBeforeUnmount(() => {
emitter.off('send-to-b')
})
</script>
TypeScript
App.vue
<template>
<BrotherA />
<BrotherB />
</template>
<script setup lang="ts">
import BrotherA from './components/BrotherA.vue'
import BrotherB from './components/BrotherB.vue'
</script>
Demo2
TypeScript
src/utils/eventBus.ts
import mitt from 'mitt'
type EventType = {
'submit-form': { username: string; password: string }
'global-notice': string
}
const emitter = mitt<EventType>()
export default emitter
TypeScript
src/composables/useEventBus.ts
import emitter from '@/utils/eventBus'
import { onBeforeUnmount } from 'vue'
export function useEventBus() {
const handlers: Array<() => void> = []
const on = <T extends keyof EventType>(event: T, callback: (data: EventType[T]) => void) => {
emitter.on(event, callback)
handlers.push(() => emitter.off(event, callback))
}
const emit = <T extends keyof EventType>(event: T, data: EventType[T]) => {
emitter.emit(event, data)
}
onBeforeUnmount(() => {
handlers.forEach(unbind => unbind())
})
return { on, emit }
}
TypeScript
FormSubmit.vue
<template>
<div style="border:1px solid #ccc;padding:12px;">
<h3>表单组件</h3>
<input v-model="form.username" placeholder="用户名"/>
<input v-model="form.password" placeholder="密码"/>
<button @click="handleSubmit">提交表单</button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useEventBus } from '@/composables/useEventBus'
const { emit } = useEventBus()
const form = ref({
username: '',
password: ''
})
const handleSubmit = () => {
emit('submit-form', form.value)
emit('global-notice', '表单已提交成功!')
}
</script>
TypeScript
GlobalTip.vue
<template>
<div style="border:1px solid #42b983;padding:12px;margin-top:10px;">
<h3>全局提示组件</h3>
<p>表单数据:{{formInfo}}</p>
<p>通知消息:{{tipMsg}}</p>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useEventBus } from '@/composables/useEventBus'
const { on } = useEventBus()
const formInfo = ref('')
const tipMsg = ref('')
on('submit-form', (data) => {
formInfo.value = `用户名:${data.username},密码:${data.password}`
})
on('global-notice', (msg) => {
tipMsg.value = msg
})
</script>
TypeScript
App.vue
<template>
<FormSubmit />
<GlobalTip />
</template>
<script setup lang="ts">
import FormSubmit from './components/FormSubmit.vue'
import GlobalTip from './components/GlobalTip.vue'
</script>
注意:
1 必须手动取消监听:组件卸载时务必调用off移除回调,否则回调函数会一直驻留内存,导致内存泄漏。
2 事件命名冲突:事件名是全局字符串,建议加命名空间前缀,避免不同模块间冲突。