Vue消息订阅与发布过程

 更新时间:2025年11月03日 09:54:26   作者:咖啡の猫  
发布-订阅模式是一种设计模式,通过事件中心实现对象间的解耦通信,在Vue中,Vue2使用Vue实例作为事件总线,Vue3推荐使用mitt库,发布-订阅模式适用于广播通知、解耦组件和复杂状态管理,在选择工具时,建议根据场景选择合适的工具

一、什么是发布-订阅模式?

发布-订阅模式 是一种对象间通信的设计模式,包含三个核心角色:

  • 发布者(Publisher):发出消息(事件)
  • 订阅者(Subscriber):监听并处理消息
  • 事件中心(Event Channel):中介,负责消息的注册与分发

类比理解

想象一个“广播电台”:

  • 电台(事件中心)负责广播
  • 主持人(发布者)说:“现在是北京时间 8 点整”
  • 听众(订阅者)听到后,各自执行动作(起床、煮咖啡、打卡)

发布者无需知道谁在听,订阅者也无需知道谁在说,实现解耦

二、核心概念:订阅、发布、取消订阅

操作方法说明
订阅on(event, callback)注册事件监听器
发布emit(event, ...args)触发事件,传递数据
取消订阅off(event, callback)移除监听,防止内存泄漏

三、在 Vue 中的实现方式

1. Vue 2:使用 Vue 实例作为事件总线

Vue 2 的实例本身实现了事件接口($on$emit$off),可直接用作事件中心。

(1) 创建 EventBus

// utils/eventBus.js
import Vue from 'vue'

// 创建一个 Vue 实例作为全局事件中心
const EventBus = new Vue()

export default EventBus

(2) 订阅消息(在组件中)

<!-- NotificationBar.vue -->
<script>
import EventBus from '@/utils/eventBus'

export default {
  data() {
    return {
      message: ''
    }
  },
  created() {
    // 订阅 'show-notification' 事件
    EventBus.$on('show-notification', (msg, type) => {
      this.message = `[${type}] ${msg}`
      this.show()
    })
  },
  beforeDestroy() {
    // 组件销毁时取消订阅,避免内存泄漏
    EventBus.$off('show-notification')
  },
  methods: {
    show() {
      // 显示通知
      console.log(this.message)
    }
  }
}
</script>

(3) 发布消息

<!-- LoginButton.vue -->
<script>
import EventBus from '@/utils/eventBus'

export default {
  methods: {
    login() {
      // 发布登录成功事件
      EventBus.$emit('user-logged-in', { id: 1, name: 'Alice' })
      
      // 发布全局通知
      EventBus.$emit('show-notification', '登录成功!', 'success')
    }
  }
}
</script>

实现了跨组件通信,但需手动管理生命周期

2. Vue 3:使用mitt库(推荐)

Vue 3 移除了实例上的 $on$off 方法,因此不能再使用 Vue 实例作为事件总线。

(1) 安装 mitt

npm install mitt

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

(2) 创建全局事件中心

// utils/eventBus.js
import mitt from 'mitt'

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

export default EventBus

(3) 在 Vue 3 组件中使用

<!-- NotificationBar.vue -->
<script setup>
import { onMounted, onUnmounted } from 'vue'
import EventBus from '@/utils/eventBus'

// 订阅事件
const handleNotification = (data) => {
  console.log(`[通知] ${data.type}: ${data.msg}`)
}

onMounted(() => {
  EventBus.on('show-notification', handleNotification)
})

// 取消订阅
onUnmounted(() => {
  EventBus.off('show-notification', handleNotification)
})
</script>
<!-- LoginButton.vue -->
<script setup>
import EventBus from '@/utils/eventBus'

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

<template>
  <button @click="login">登录</button>
</template>

mitt 支持 TypeScript,API 简洁:onemitoffclear

四、手写一个简易的 Event Hub

为了理解原理,我们来实现一个极简版的事件中心:

// utils/EventHub.js
class EventHub {
  constructor() {
    this.events = {}
  }

  // 订阅
  on(event, callback) {
    if (!this.events[event]) {
      this.events[event] = []
    }
    this.events[event].push(callback)
  }

  // 发布
  emit(event, ...args) {
    if (this.events[event]) {
      this.events[event].forEach(callback => callback(...args))
    }
  }

  // 取消订阅
  off(event, callback) {
    if (this.events[event]) {
      this.events[event] = this.events[event].filter(cb => cb !== callback)
    }
  }

  // 清除所有
  clear(event) {
    if (event) {
      delete this.events[event]
    } else {
      this.events = {}
    }
  }
}

export default new EventHub()

核心就是维护一个事件-回调函数的映射表。

五、发布-订阅 vs 其他通信方式

通信方式方向耦合度适用场景
发布-订阅任意广播通知、解耦组件
props / emit父↔子紧密相关的父子组件
Pinia任意共享状态、复杂逻辑
Provide/Inject祖→孙主题、配置传递

选择建议:

  • 状态共享 → Pinia
  • 简单通知 → mitt
  • 祖先传值 → provide/inject

六、最佳实践与注意事项

1. 使用语义化事件名

// ❌ 避免模糊命名
EventBus.emit('click')

// ✅ 使用前缀和语义化命名
EventBus.emit('user:login-success', user)
EventBus.emit('cart:item-added', item)

2. 务必取消订阅

// Vue 2
beforeDestroy() {
  EventBus.$off('event-name')
}

// Vue 3 Composition API
onUnmounted(() => {
  EventBus.off('event-name', handler)
})

忘记 off 会导致内存泄漏重复触发

3. 避免过度使用

  • 不要用事件总线代替状态管理
  • 复杂状态同步 → 使用 Pinia
  • 事件过多 → 考虑模块化事件中心

4. TypeScript 支持

// types/events.ts
type Events = {
  'user:login': (user: User) => void
  'cart:update': (count: number) => void
  'app:loading': (status: boolean) => void
}

// utils/eventBus.ts
import mitt from 'mitt'
export const useEventBus = () => mitt<Events>()

类型安全,IDE 自动提示。

七、现代替代方案:Pinia + Actions

对于大多数场景,Pinia 是更优解:

// stores/appStore.ts
import { defineStore } from 'pinia'

export const useAppStore = defineStore('app', {
  state: () => ({
    user: null,
    cartCount: 0,
    isLoading: false
  }),
  actions: {
    login(user) {
      this.user = user
      // 其他组件自动响应
    },
    addToCart(item) {
      this.cartCount++
    },
    setLoading(status) {
      this.isLoading = status
    }
  }
})
<!-- 任何组件 -->
<script setup>
import { useAppStore } from '@/stores/appStore'

const appStore = useAppStore()

// 直接调用 action
appStore.login(userData)
</script>

响应式、可调试、支持 SSR。

八、总结

核心点说明
本质解耦的事件驱动通信
角色发布者、订阅者、事件中心
Vue 2new Vue() 作为 EventBus
Vue 3使用 mitt 库
优点灵活、解耦、轻量
缺点难以追踪、易内存泄漏
推荐简单通知用 mitt,状态用 Pinia

一句话总结:

发布-订阅是“消息广播”,而 Pinia 是“状态中心”——根据场景选择合适的工具。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Vue3中pinia用法示例

    Vue3中pinia用法示例

    这篇文章主要介绍了Vue3中使用pinia,本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-07-07
  • VUE使用 wx-open-launch-app 组件开发微信打开APP功能

    VUE使用 wx-open-launch-app 组件开发微信打开APP功能

    这篇文章主要介绍了VUE使用 wx-open-launch-app 组件开发微信打开APP功能,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-08-08
  • vue使用codemirror的两种用法

    vue使用codemirror的两种用法

    这篇文章主要介绍了在vue里使用codemirror的两种用法,每种方法通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-08-08
  • Element中Slider滑块的具体使用

    Element中Slider滑块的具体使用

    这篇文章主要介绍了Element中Slider滑块的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-07-07
  • Vue中引入swiper报错的问题及解决

    Vue中引入swiper报错的问题及解决

    这篇文章主要介绍了Vue中引入swiper报错的问题及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-10-10
  • vue中v-for 循环对象中的属性

    vue中v-for 循环对象中的属性

    这篇文章主要介绍了 vue中v-for 循环对象中的属性,文章围绕v-for 循环对象的相关资料展开详细内容,需要的朋友可以参考一下,希望对大家有所帮助
    2021-11-11
  • Vue循环中多个input绑定指定v-model实例

    Vue循环中多个input绑定指定v-model实例

    这篇文章主要介绍了Vue循环中多个input绑定指定v-model实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-08-08
  • vue-router的使用方法及含参数的配置方法

    vue-router的使用方法及含参数的配置方法

    这篇文章主要介绍了vue-router的使用方法及含参数的配置方法,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下
    2018-11-11
  • 详解vuex的简单使用

    详解vuex的简单使用

    这篇文章主要介绍了详解vuex的简单使用,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-03-03
  • vue3中v-model的用法详解

    vue3中v-model的用法详解

    vue 提供了很多自定义指令,大大方便了我们的开发,其中最常用的也就是 v-model,他可以在组件上使用以实现双向绑定。它可以绑定多种数据结构, 今天总结一下用法
    2023-04-04

最新评论