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 是“状态中心”——根据场景选择合适的工具。

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

相关文章

  • 详解ElementUI之表单验证、数据绑定、路由跳转

    详解ElementUI之表单验证、数据绑定、路由跳转

    本篇文章主要介绍了ElementUI之表单验证、数据绑定、路由跳转,非常具有实用价值,需要的朋友可以参考下
    2017-06-06
  • Element UI响应式布局问题的解决

    Element UI响应式布局问题的解决

    本文主要介绍了Element UI响应式布局问题的解决,提供了五种屏幕大小尺寸,xs、sm、md、lg 和 xl,并对这五种尺寸提供了两种解决方案,感兴趣的可以了解一下
    2025-08-08
  • element ui el-calendar日历组件使用方法总结

    element ui el-calendar日历组件使用方法总结

    这篇文章主要给大家介绍了关于element ui el-calendar日历组件使用方法的相关资料,elementui是一款基于Vue.js的UI框架,其中的日历组件calendar是elementui中非常常用的组件之一,需要的朋友可以参考下
    2023-07-07
  • Vue3中样式渗透:deep()无效的原因分析

    Vue3中样式渗透:deep()无效的原因分析

    今天学习 /deep/ 样式穿透,因为vue3中已经使用:deep()取代了/deep/ ,所以直接用:deep()练习,这篇文章主要介绍了Vue3中样式渗透:deep()为什么无效,需要的朋友可以参考下
    2022-11-11
  • vuex进阶知识点巩固

    vuex进阶知识点巩固

    本片文章是小编在网上整理的关于VUEX进阶的相关知识点内容,对此有兴趣的朋友可以跟着学习参考下。
    2018-05-05
  • vue中的vendor.js文件过大问题及解决

    vue中的vendor.js文件过大问题及解决

    这篇文章主要介绍了vue中的vendor.js文件过大问题及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-08-08
  • Vue中父子组件通讯之todolist组件功能开发

    Vue中父子组件通讯之todolist组件功能开发

    这篇文章主要介绍了Vue中父子组件通讯——todolist组件功能开发的相关知识,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下
    2018-05-05
  • vue中的Canvas使用及说明

    vue中的Canvas使用及说明

    文章详细介绍了HTML5 Canvas的模板区、样式区和脚本区的使用方法,包括如何设置Canvas的宽高、水平居中、获取Canvas元素、绘制图形(直线、矩形、圆、文字)、转换为Base64、下载图片以及加载图片并添加文字水印等操作
    2025-10-10
  • Vue.js实现表格渲染的方法

    Vue.js实现表格渲染的方法

    今天小编就为大家分享一篇对Vue.js实现表格渲染的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-09-09
  • 在Vue3项目中使用VueCropper裁剪组件实现裁剪及预览效果

    在Vue3项目中使用VueCropper裁剪组件实现裁剪及预览效果

    这篇文章主要介绍了在Vue3项目中使用VueCropper裁剪组件(裁剪及预览效果),本文分步骤结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-07-07

最新评论