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

八、结语

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

相关推荐
Lovereo3 小时前
我的目标检测性能优化之路:预算不够、GPU 没有、但性能我得要
前端
T___T3 小时前
JavaScript 变量声明详解:var、let、const 的核心差异
javascript·面试
蒙娜丽宁3 小时前
Rust 与 WebAssembly:构建高效前端应用的全流程复盘
前端·rust·wasm
这儿有一堆花3 小时前
使用 Actix-web 开发高性能 Web 服务
前端·数据库
豆苗学前端3 小时前
10分钟带你入门websocket,并实现一个在线多人聊天室
前端·javascript·后端
白水清风3 小时前
Vue3之渲染器
前端·vue.js·面试
刘永胜是我3 小时前
解决Volta环境下npm全局包卸载失败:一次深入排查之旅
前端·node.js
白水清风3 小时前
Vue3之组件化
前端·vue.js·面试
luckyPian3 小时前
ES6+新特性:ES7(二)
开发语言·javascript·ecmascript