Vue全局事件总线

一、什么是全局事件总线?

全局事件总线 是一个独立的事件中心 ,任何组件都可以向它发送(emit)事件 ,也可以监听(on)事件,从而实现任意组件间的通信。

它类似于一个"广播站":

  • 组件 A 发送消息:"用户已登录"
  • 组件 B、C、D 监听到后,执行相应逻辑

二、Vue 2 中的事件总线实现

在 Vue 2 中,可以利用 Vue 实例本身就是一个事件系统的特点,创建一个全局的 Vue 实例作为事件总线。

1. 创建 EventBus

javascript 复制代码
// utils/eventBus.js
import Vue from 'vue'

// 创建一个 Vue 实例作为事件总线
const EventBus = new Vue()

export default EventBus

2. 组件 A:发送事件

html 复制代码
<!-- LoginButton.vue -->
<template>
  <button @click="login">登录</button>
</template>

<script>
import eventBus from '@/utils/eventBus'

export default {
  methods: {
    login() {
      // 模拟登录成功
      const user = { id: 1, name: 'Alice' }
      
      // 发送事件
      eventBus.$emit('user-logged-in', user)
      
      // 也可携带多个参数
      eventBus.$emit('show-toast', '登录成功!', 'success')
    }
  }
}
</script>

3. 组件 B:监听事件

html 复制代码
<!-- Sidebar.vue -->
<template>
  <div>
    <p>欢迎,{{ userName }}</p>
  </div>
</template>

<script>
import eventBus from '@/utils/eventBus'

export default {
  data() {
    return {
      userName: '游客'
    }
  },
  created() {
    // 监听登录事件
    eventBus.$on('user-logged-in', (user) => {
      this.userName = user.name
    })

    // 监听提示事件
    eventBus.$on('show-toast', (text, type) => {
      this.showToast(text, type)
    })
  },
  methods: {
    showToast(text, type) {
      // 显示提示(可集成 toast 插件)
      console.log(`[${type}] ${text}`)
    }
  },
  // 组件销毁时,务必解绑事件,避免内存泄漏!
  beforeDestroy() {
    eventBus.$off('user-logged-in')
    eventBus.$off('show-toast')
  }
}
</script>

✅ 实现了跨组件通信。

三、Vue 3 中的挑战与替代方案

Vue 3 移除了 $on$off$once 等实例方法,因此 无法再使用 Vue 实例作为事件总线

解决方案:使用 mitt

mitt 是一个超轻量(<200B)的事件发射器,完美替代 Vue 2 的 EventBus。

1. 安装 mitt
bash 复制代码
npm install mitt
2. 创建全局事件总线
javascript 复制代码
// utils/eventBus.js
import mitt from 'mitt'

// 创建事件中心
const EventBus = mitt()

export default EventBus
3. 组件中使用(Vue 3 + Composition API)
html 复制代码
<!-- LoginButton.vue -->
<script setup>
import { ref } from 'vue'
import EventBus from '@/utils/eventBus'

const user = ref(null)

function login() {
  user.value = { id: 1, name: 'Bob' }
  
  // 发送事件
  EventBus.emit('user-logged-in', user.value)
  EventBus.emit('show-toast', '登录成功!', 'success')
}
</script>

<template>
  <button @click="login">登录</button>
</template>
html 复制代码
<!-- Sidebar.vue -->
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import EventBus from '@/utils/eventBus'

const userName = ref('游客')

// 监听事件
const handleLogin = (user) => {
  userName.value = user.name
}

const handleToast = (text, type) => {
  console.log(`[${type}] ${text}`)
}

onMounted(() => {
  EventBus.on('user-logged-in', handleLogin)
  EventBus.on('show-toast', handleToast)
})

// 组件卸载时解绑
onUnmounted(() => {
  EventBus.off('user-logged-in', handleLogin)
  EventBus.off('show-toast', handleToast)
})
</script>

<template>
  <div>
    <p>欢迎,{{ userName }}</p>
  </div>
</template>

mitt API 简洁:onemitoffclear

四、事件总线的优缺点

优点 缺点
灵活:任意组件可通信 难以追踪:事件来源和去向不清晰
解耦:发送方无需知道接收方 命名冲突:多个模块使用相同事件名
轻量:代码少,上手快 内存泄漏 :忘记 off 会导致事件堆积
适合一次性通知 不适合复杂状态管理

📌 核心问题:事件总线让代码变得"隐式"和"不可预测",不利于大型项目维护。

五、现代替代方案(推荐 ✅)

虽然事件总线简单,但在现代 Vue 项目中,更推荐以下方案:

1. Pinia(推荐):状态驱动 + 监听

TypeScript 复制代码
// stores/userStore.js
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    user: null,
    isLoggedIn: false
  }),
  actions: {
    login(userData) {
      this.user = userData
      this.isLoggedIn = true
      // 状态变化自动通知所有使用该 store 的组件
    }
  }
})
html 复制代码
<!-- Sidebar.vue -->
<script setup>
import { useUserStore } from '@/stores/userStore'
import { storeToRefs } from 'pinia'

const userStore = useUserStore()
const { user, isLoggedIn } = storeToRefs(userStore)
</script>

<template>
  <div v-if="isLoggedIn">
    欢迎,{{ user.name }}
  </div>
</template>

✅ 状态集中管理,响应式更新,调试工具支持。


2. Provide / Inject:祖先 → 后代通信

html 复制代码
<!-- App.vue -->
<script setup>
import { provide, ref } from 'vue'

const theme = ref('light')
const toggleTheme = () => {
  theme.value = theme.value === 'light' ? 'dark' : 'light'
}

// 提供给所有后代
provide('theme', theme)
provide('toggleTheme', toggleTheme)
</script>
html 复制代码
<!-- AnyChildComponent.vue -->
<script setup>
import { inject } from 'vue'

const theme = inject('theme')
const toggleTheme = inject('toggleTheme')
</script>

<template>
  <button @click="toggleTheme">
    切换到{{ theme === 'light' ? '深色' : '浅色' }}模式
  </button>
</template>

✅ 适合主题、配置等全局状态。


3. 自定义 Hook + mitt(轻量场景)

对于简单的通知(如 toast、modal),可以封装 useEventBus

TypeScript 复制代码
// composables/useEventBus.ts
import mitt from 'mitt'
type Events = {
  'show-toast': (text: string, type?: string) => void
  'open-modal': (id: string) => void
}

export const useEventBus = () => mitt<Events>()

然后在需要的地方使用。

六、何时使用事件总线?

场景 建议
Vue 2 项目 ✅ 可使用(注意解绑)
Vue 3 项目 ⚠️ 仅用于简单通知,优先考虑 Pinia
一次性通知 ✅ 如:show-toastroute-changed
复杂状态同步 ❌ 使用 Pinia
大型项目 ❌ 避免,增加维护成本

七、总结

方案 适用场景 推荐度
EventBus (Vue 2) Vue 2 项目简单通信 ⚠️ 仅限老项目
mitt Vue 3 轻量事件通知 ✅ 适合通知类
Pinia 状态共享、复杂逻辑 ✅✅✅ 首选
Provide/Inject 祖先→后代传递配置 ✅ 适合主题、语言等

📌 核心结论

  • 事件总线是"快捷方式",不是"最佳实践"
  • 在 Vue 3 项目中,优先使用 Pinia 或 Composition API
  • 仅在轻量级、一次性通知 场景下考虑 mitt

八、结语

感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!

相关推荐
崔庆才丨静觅18 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606119 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了19 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅19 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅19 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅20 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment20 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅20 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊20 小时前
jwt介绍
前端
爱敲代码的小鱼20 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax