需求背景

在 List 列表页面中:

  • 每行数据的操作栏有一个按钮
  • 点击按钮会打开对应的 Drawer(抽屉)组件
  • Drawer 是独立组件,每行数据对应一个 Drawer 实例
  • 核心需求:当打开新的 Drawer 时,自动关闭之前打开的 Drawer

解决方案对比

方案1:父子组件通信

实现思路

  • 在父组件中维护当前活跃的 Drawer ID
  • 子组件打开时通知父组件更新状态
  • 父组件通过 props 或事件通知其他子组件关闭

代码示例

// 父组件
data() {
  return {
    activeDrawerId: null
  }
},
methods: {
  setActiveDrawer(id) {
    this.activeDrawerId = id
  }
}

// 子组件
props: ['activeId', 'drawerId'],
watch: {
  activeId(newVal) {
    if (newVal !== this.drawerId) {
      this.close()
    }
  }
}

优缺点

  • ✅ 符合 Vue 数据流原则
  • ❌ 父子组件耦合度高
  • ❌ 需要额外的 props 传递
  • ❌ 维护成本较高

方案2:根组件事件总线

实现思路

  • 利用 Vue 根实例 ($root) 作为事件中心
  • 每个 Drawer 组件创建时注册事件监听
  • 打开新 Drawer 时通过事件通知其他组件关闭

代码示例

created() {
  this.closeListener = (uid) => {
    if (this._uid !== uid) this.close()
  }
  this.$root.$on('close-drawer', this.closeListener)
},

beforeDestroy() {
  this.$root.$off('close-drawer', this.closeListener)
},

methods: {
  openDrawer() {
    this.$root.$emit('close-drawer', this._uid)
  }
}

优缺点

  • ✅ 实现简单,代码量少
  • ✅ 组件间解耦
  • ❌ 污染全局根实例
  • ❌ Vue 3 中不再支持
  • ❌ 事件命名可能冲突

方案3:Vuex 状态管理

实现思路

  • 使用 Vuex 管理全局状态
  • 通过状态变化驱动组件行为
  • 组件响应状态变化自动关闭

代码示例

// store
state: {
  activeDrawerId: null
},
mutations: {
  SET_ACTIVE_DRAWER(state, id) {
    state.activeDrawerId = id
  }
}

// 组件
computed: {
  shouldClose() {
    return this.$store.state.activeDrawerId !== this._uid
  }
},
watch: {
  shouldClose(val) {
    if (val) this.close()
  }
}

优缺点

  • ✅ 状态可追踪,DevTools 支持
  • ✅ 响应式自动更新
  • ❌ 相对重量级
  • ❌ 对于简单需求过度设计

方案4:专用 Event Bus

实现思路

  • 创建独立的 Vue 实例作为事件总线
  • 专门处理组件间通信
  • 实现原理与根组件事件相同,但更规范

代码示例

// event-bus.js
import Vue from 'vue'
export const DrawerEventBus = new Vue()

// 组件
import { DrawerEventBus } from './event-bus'

created() {
  DrawerEventBus.$on('close-drawer', this.closeListener)
},

beforeDestroy() {
  DrawerEventBus.$off('close-drawer', this.closeListener)
},

methods: {
  openDrawer() {
    DrawerEventBus.$emit('close-drawer', this._uid)
  }
}

优缺点

  • ✅ 职责单一,专门用于通信
  • ✅ 不污染业务组件
  • ✅ 易于维护和扩展
  • ❌ 需要创建额外文件

方案推荐

根据项目规模选择:

项目规模 推荐方案 理由
小型项目 根组件事件 快速实现,无需额外配置
中型项目 Event Bus 职责分离,便于维护
大型项目 Vuex 状态管理,调试友好

技术演进考虑:

  • Vue 2 项目:Event Bus 是最佳选择
  • Vue 3 项目:推荐使用 mitt 或 Provide/Inject

核心要点

  1. 问题本质:跨组件通信,实现”单例模式”的 UI 组件
  2. 关键机制:事件广播 + 自过滤 (this._uid)
  3. 架构原则:高内聚、低耦合,业务逻辑封装在组件内部

总结

对于这个需求,Event Bus 方案在可维护性和实现复杂度之间取得了最佳平衡,是推荐的解决方案。它既保持了组件的独立性,又提供了清晰的通信机制,适合大多数前端项目。