Files
vue-desktop/.qoder/repowiki/zh/content/事件系统/核心事件总线.md
2025-09-24 16:43:10 +08:00

207 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 核心事件总线
<cite>
**本文档引用的文件**
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts)
- [IEventBuilder.ts](file://src/events/IEventBuilder.ts)
- [IDestroyable.ts](file://src/common/types/IDestroyable.ts)
- [EventManager.ts](file://src/events/EventManager.ts)
- [DesktopEventManager.ts](file://src/events/DesktopEventManager.ts)
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts)
</cite>
## 目录
1. [简介](#简介)
2. [核心数据结构设计](#核心数据结构设计)
3. [泛型类型安全机制](#泛型类型安全机制)
4. [事件监听器管理](#事件监听器管理)
- [addEventListener 方法详解](#addeventlistener-方法详解)
- [removeEventListener 方法详解](#removeeventlistener-方法详解)
5. [事件通知与分发](#事件通知与分发)
6. [资源清理与内存泄漏防范](#资源清理与内存泄漏防范)
7. [实际应用示例](#实际应用示例)
8. [总结](#总结)
## 简介
`EventBuilderImpl` 类是本系统事件机制的核心实现,提供了一个类型安全、功能完整的事件总线系统。该类实现了 `IEventBuilder` 接口,遵循发布-订阅模式,支持事件的注册、移除和触发,并具备立即执行、一次性监听等高级特性。通过继承 `IDestroyable` 接口,它还提供了资源清理能力,有效防止内存泄漏。
该事件系统被多个模块广泛使用,包括全局事件管理器(`eventManager`)、桌面事件管理器(`desktopEM`)和窗口表单事件管理器(`wfem`),构成了整个应用的通信骨架。
**Section sources**
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L7-L95)
- [IEventBuilder.ts](file://src/events/IEventBuilder.ts#L13-L46)
- [IDestroyable.ts](file://src/common/types/IDestroyable.ts#L4-L7)
## 核心数据结构设计
`EventBuilderImpl` 采用 `Map``Set` 的组合来高效管理事件监听器,这种设计在性能和内存使用上达到了良好的平衡。
其核心是一个私有成员 `_eventHandlers`,其类型为 `Map<keyof Events, Set<HandlerWrapper<Events[keyof Events]>>>`。这个数据结构的含义是:
- **外层 Map**:以事件名称(`keyof Events`)作为键,确保每个事件名对应一个独立的监听器集合。
- **内层 Set**:存储特定事件的所有监听器包装对象(`HandlerWrapper`)。使用 `Set` 而非数组可以天然避免重复添加同一个监听函数,并且插入和删除操作的时间复杂度为 O(1)。
`HandlerWrapper` 是一个简单的接口,包含两个属性:`fn`(原始的监听函数)和 `once`(布尔值,标记是否为一次性监听器)。这种包装方式将业务逻辑(函数本身)与元数据(如 `once` 标志)分离,使得事件管理更加灵活。
```mermaid
classDiagram
class EventBuilderImpl~Events~ {
-_eventHandlers : Map~keyof Events, Set~HandlerWrapper~Events[keyof Events]~~~
+addEventListener()
+removeEventListener()
+notifyEvent()
+destroy()
}
class HandlerWrapper~T~ {
+fn : T
+once : boolean
}
EventBuilderImpl --> "0..*" HandlerWrapper : 包含
```
**Diagram sources**
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L7-L15)
**Section sources**
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L7-L15)
## 泛型类型安全机制
`EventBuilderImpl` 类通过 TypeScript 的泛型系统实现了严格的类型安全。其定义为 `class EventBuilderImpl<Events extends IEventMap>`,其中 `Events` 是一个必须符合 `IEventMap` 接口约束的泛型类型。
`IEventMap` 接口定义了事件映射的基本结构:一个索引签名 `[key: string]: (...args: any[]) => void`,表示键是字符串类型的事件名,值是任意参数的无返回值函数。
当实例化 `EventBuilderImpl` 时,需要传入一个具体的事件接口。例如,在 `WindowFormEventManager.ts` 中定义了 `IWindowFormEvent` 接口,并将其作为泛型参数:
```typescript
export const wfem = new EventBuilderImpl<IWindowFormEvent>()
```
这种设计带来了以下优势:
1. **编译时检查**:在调用 `addEventListener``notifyEvent`TypeScript 编译器会根据 `IWindowFormEvent` 的定义检查事件名和参数类型是否正确。
2. **智能提示**:开发者在编写代码时能获得准确的事件名和参数类型提示。
3. **防止错误**:无法订阅未在接口中定义的事件,也无法传递错误类型的参数。
**Section sources**
- [IEventBuilder.ts](file://src/events/IEventBuilder.ts#L4-L7)
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L5-L59)
## 事件监听器管理
### addEventListener 方法详解
`addEventListener` 方法用于向事件总线注册新的监听器。它接收三个参数:事件名、处理函数和可选的配置项。
#### 配置项行为逻辑
- **immediate (立即执行)**:如果设置为 `true`,则在添加监听器后立即同步执行一次该函数。
- **immediateArgs (立即执行参数)**:为 `immediate` 执行阶段提供参数。若未指定,则使用空数组。
- **once (一次性监听)**:如果设置为 `true`,则该监听器在第一次被触发后自动从事件队列中移除。
#### 实现细节
1. **空值检查**:首先检查 `handler` 是否存在,避免无效监听器。
2. **惰性初始化**:如果这是该事件的第一个监听器,则创建一个新的 `Set` 来存放后续的 `HandlerWrapper`
3. **去重机制**:在添加前,通过遍历 `Set` 并比较 `wrapper.fn === handler` 来确保不会重复添加相同的函数引用。
4. **异常捕获**:在执行 `immediate` 回调时,使用 `try-catch` 捕获任何可能抛出的异常,并将其输出到控制台,防止因单个监听器的错误而中断主流程。
```mermaid
flowchart TD
Start([开始]) --> CheckHandler["检查 handler 是否为空"]
CheckHandler --> |否| InitSet["检查事件对应的 Set 是否存在"]
InitSet --> |否| CreateSet["创建新的 Set"]
CreateSet --> GetSet["获取事件对应的 Set"]
InitSet --> |是| GetSet
GetSet --> CheckDuplicate["检查是否已存在相同 handler"]
CheckDuplicate --> |否| AddWrapper["添加 HandlerWrapper 到 Set"]
AddWrapper --> CheckImmediate["检查 immediate 选项"]
CheckImmediate --> |是| ExecuteNow["立即执行 handler"]
ExecuteNow --> End([结束])
CheckImmediate --> |否| End
CheckHandler --> |是| End
CheckDuplicate --> |是| End
```
**Diagram sources**
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L20-L46)
**Section sources**
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L20-L46)
### removeEventListener 方法详解
`removeEventListener` 方法负责从事件队列中移除指定的监听器。
其实现的关键在于**引用比对去重机制**。由于监听器是以 `HandlerWrapper` 对象的形式存储在 `Set` 中的,直接比较 `Set` 中的对象与传入的 `handler` 函数是不相等的。因此,该方法会遍历 `Set` 中的每一个 `wrapper`,并使用 `wrapper.fn === handler` 来精确匹配原始的函数引用。
一旦找到匹配项,就调用 `set.delete(wrapper)` 将其从 `Set` 中移除。这种基于引用的比对确保了只有完全相同的函数实例才会被移除,保证了操作的准确性。
**Section sources**
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L55-L64)
## 事件通知与分发
`notifyEvent` 方法是事件系统的触发点,负责通知所有订阅了特定事件的监听器。
其工作流程如下:
1. **存在性检查**:首先检查是否存在该事件名对应的监听器集合,若不存在则直接返回。
2. **批量通知**:遍历该事件名对应 `Set` 中的所有 `HandlerWrapper`
3. **异常捕获**:在调用每个监听器的 `fn(...args)` 时,使用 `try-catch` 块包裹,确保单个监听器的错误不会影响其他监听器的执行。
4. **once 监听器清理**:在成功调用一个监听器后,检查其 `once` 标志。如果为 `true`,则立即将其从 `Set` 中删除,实现一次性监听的功能。
这种“先通知,后清理”的策略保证了即使在 `once` 监听器执行过程中有其他代码尝试移除它,也不会产生竞态条件。
```mermaid
sequenceDiagram
participant Publisher as 事件发布者
participant EventBus as EventBuilderImpl
participant ListenerA as 监听器 A (once : true)
participant ListenerB as 监听器 B (once : false)
Publisher->>EventBus : notifyEvent('click', x, y)
EventBus->>ListenerA : 执行 fn(x, y)
ListenerA-->>EventBus : 完成
EventBus->>EventBus : 检查 wrapper.once == true
EventBus->>EventBus : 从 Set 中删除 ListenerA
EventBus->>ListenerB : 执行 fn(x, y)
ListenerB-->>EventBus : 完成
EventBus-->>Publisher : 通知完成
```
**Diagram sources**
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L75-L90)
**Section sources**
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L75-L90)
## 资源清理与内存泄漏防范
`EventBuilderImpl` 通过多种策略有效防范内存泄漏:
1. **显式销毁接口**:通过实现 `IDestroyable` 接口,提供了 `destroy()` 方法。调用此方法会清空 `_eventHandlers` Map释放所有对监听器函数的引用使它们可以被垃圾回收器回收。这对于长生命周期的应用或动态创建/销毁的组件至关重要。
2. **监听器去重**`addEventListener` 方法中的去重逻辑防止了同一函数被多次添加,避免了不必要的内存占用和重复执行。
3. **once 监听器自动清理**`once` 选项确保了一次性监听器在执行后立即被移除,无需手动清理。
4. **异常隔离**`try-catch` 机制保证了事件分发过程的健壮性,防止因监听器内部错误导致整个事件系统崩溃。
**Section sources**
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L92-L94)
- [IDestroyable.ts](file://src/common/types/IDestroyable.ts#L4-L7)
## 实际应用示例
以下是 `EventBuilderImpl` 在项目中的具体应用实例:
- **全局事件管理器**:在 `EventManager.ts` 中,`eventManager` 实例被用来管理认证状态改变 (`onAuthChange`) 和主题切换 (`onThemeChange`) 等全局事件。
- **桌面事件管理器**:在 `DesktopEventManager.ts` 中,`desktopEM` 实例用于响应桌面应用图标位置的变化 (`desktopAppPosChange`)。
- **窗口表单事件管理器**:在 `WindowFormEventManager.ts` 中,`wfem` 实例监控窗口的最小化、最大化、关闭等生命周期事件。
这些预定义的事件管理器实例化了 `EventBuilderImpl`,并通过具体的事件接口(如 `IWindowFormEvent`)锁定了可用的事件集,为不同模块提供了清晰、类型安全的通信契约。
**Section sources**
- [EventManager.ts](file://src/events/EventManager.ts#L3-L34)
- [DesktopEventManager.ts](file://src/events/DesktopEventManager.ts#L3-L15)
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L3-L60)
## 总结
`EventBuilderImpl` 作为一个轻量级但功能完备的事件总线实现,通过精心设计的数据结构(`Map`+`Set`)和 TypeScript 泛型,实现了高性能、类型安全的事件管理。其 `addEventListener``removeEventListener``notifyEvent` 方法构成了一个健壮的发布-订阅循环,而 `immediate``once` 等选项以及异常捕获机制则增强了其实用性和可靠性。最后,通过 `IDestroyable` 接口提供的 `destroy` 方法,确保了资源的可管理性,有效防止了内存泄漏,是构建可维护前端应用的理想选择。