vue.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > vue.js > Vue消息订阅与发布

Vue消息订阅与发布过程

作者:咖啡の猫

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

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

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

类比理解

想象一个“广播电台”:

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

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

操作方法说明
订阅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祖→孙主题、配置传递

选择建议:

六、最佳实践与注意事项

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. 避免过度使用

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

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

您可能感兴趣的文章:
阅读全文