Vue 组件间通信方案:实现 Drawer 单例模式
需求背景
在 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
核心要点
- 问题本质:跨组件通信,实现”单例模式”的 UI 组件
- 关键机制:事件广播 + 自过滤 (
this._uid
) - 架构原则:高内聚、低耦合,业务逻辑封装在组件内部
总结
对于这个需求,Event Bus 方案在可维护性和实现复杂度之间取得了最佳平衡,是推荐的解决方案。它既保持了组件的独立性,又提供了清晰的通信机制,适合大多数前端项目。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 玲辰书斋!