EventBus
事件总线,用于在 diagram-js 实例中进行跨组件通信。
模块说明
EventBus 是 diagram-js 的核心通信机制,所有的交互、状态变更和组件间通信都通过事件总线进行。它实现了观察者模式,允许组件监听和触发事件。
核心特性
- 优先级支持: 监听器可以指定优先级(数值越大优先级越高)
- 事件传播控制: 支持停止传播和阻止默认行为
- 错误处理: 内置错误处理机制
- 一次性监听: 支持
once()方法注册一次性监听器 - 批量注册: 可以一次性注册多个事件
模块依赖
此模块无外部依赖:
// 无 $inject 属性TypeScript 类型
/**
* 事件对象接口
*/
interface Event {
/** 事件类型 */
type?: string;
/** 停止事件传播 */
stopPropagation(): void;
/** 阻止默认行为 */
preventDefault(): void;
/** 是否已取消冒泡 */
cancelBubble: boolean;
/** 是否已阻止默认行为 */
defaultPrevented: boolean;
/** 返回值 */
returnValue: any;
}
/**
* 事件回调函数
* @template E - 事件数据类型
*/
type EventBusEventCallback<E> = (event: E & Event, ...args: any[]) => any;
/**
* 事件监听器内部结构
*/
interface EventBusListener {
/** 优先级 */
priority: number;
/** 下一个监听器(链表结构) */
next: EventBusListener | null;
/** 回调函数 */
callback: EventBusEventCallback<any>;
}私有属性
_listeners
类型: Record<string, EventBusListener>
说明: 存储所有事件监听器的对象,使用事件名作为键,监听器链表作为值。监听器按优先级排序(高优先级在前)。
私有方法
_destroy()
作用: 清理所有事件监听器。
说明: 在 diagram.destroy 事件触发时自动调用,清空 _listeners 对象。
_invokeListeners()
作用: 调用监听器链表中的所有监听器。
参数:
event{Event}: 事件对象args{any[]}: 传递给监听器的参数数组listener{EventBusListener}: 第一个监听器
返回值: {any} - 最后一个监听器的返回值
说明: 按顺序调用监听器链表,直到事件被停止传播或链表结束。
_invokeListener()
作用: 调用单个监听器。
参数:
event{Event}: 事件对象args{any[]}: 传递给监听器的参数数组listener{EventBusListener}: 要调用的监听器
返回值: {any} - 监听器的返回值
说明:
- 如果返回值不是
undefined,会停止事件传播 - 如果返回值是
false,会阻止默认行为 - 捕获并处理监听器中的错误
_addListener()
作用: 添加新的监听器到监听器链表中。
参数:
event{string}: 事件名称newListener{EventBusListener}: 新的监听器对象
说明: 监听器按优先级排序插入,相同优先级的监听器按注册顺序排列(先注册先执行)。
_removeListener()
作用: 从监听器链表中移除监听器。
参数:
event{string}: 事件名称callback{Function}(可选): 要移除的回调函数,如果不提供则移除该事件的所有监听器
说明: 通过比较回调函数引用来查找和移除监听器。
_getListeners()
作用: 获取指定事件的监听器链表。
参数:
name{string}: 事件名称
返回值: {EventBusListener} - 监听器链表的头节点
_setListeners()
作用: 设置指定事件的监听器链表。
参数:
name{string}: 事件名称listener{EventBusListener}: 监听器链表的头节点
公共方法
on()
作用: 注册事件监听器。
参数:
events{string | string[]}: 要监听的事件名称或事件名称数组priority{number}(可选): 优先级,默认为1000,数值越大优先级越高callback{EventBusEventCallback}: 回调函数that{any}(可选): 回调函数的this上下文
说明:
- 回调函数的第一个参数是事件对象,后续参数是
fire()传递的额外参数 - 返回
false会阻止默认行为 - 返回非
undefined值会停止事件传播 - 相同优先级的监听器按注册顺序执行
示例:
const eventBus = diagram.get("eventBus");
// 基本用法
eventBus.on("element.click", function (event) {
console.log("元素被点击:", event.element);
});
// 带优先级
eventBus.on("element.move", 1500, function (event) {
console.log("高优先级监听器");
});
// 带上下文
eventBus.on(
"foo",
function (event) {
this.doSomething();
},
this,
);
// 监听多个事件
eventBus.on(["element.added", "element.removed"], function (event) {
console.log("元素变化:", event.type);
});
// 带额外参数
eventBus.on("custom.event", function (event, param1, param2) {
console.log(param1, param2);
});
// 控制事件传播
eventBus.on("element.delete", function (event) {
// 停止传播
event.stopPropagation();
// 阻止默认行为
event.preventDefault();
// 或者直接返回 false
return false;
});once()
作用: 注册一次性事件监听器,触发一次后自动移除。
参数:
events{string | string[]}: 要监听的事件名称或事件名称数组priority{number}(可选): 优先级,默认为1000callback{EventBusEventCallback}: 回调函数that{any}(可选): 回调函数的this上下文
示例:
const eventBus = diagram.get("eventBus");
// 只监听一次
eventBus.once("diagram.init", function (event) {
console.log("图表已初始化");
});
// 带优先级
eventBus.once("element.added", 1500, function (event) {
console.log("第一个元素被添加");
});off()
作用: 移除事件监听器。
参数:
events{string | string[]}: 要移除监听器的事件名称或事件名称数组callback{EventBusEventCallback}(可选): 要移除的回调函数,如果不提供则移除该事件的所有监听器
示例:
const eventBus = diagram.get("eventBus");
function handler(event) {
console.log("处理事件");
}
// 注册监听器
eventBus.on("foo", handler);
// 移除特定监听器
eventBus.off("foo", handler);
// 移除事件的所有监听器
eventBus.off("foo");
// 移除多个事件的监听器
eventBus.off(["foo", "bar"], handler);fire()
作用: 触发事件。
参数:
type{string}: 事件类型data{Object}(可选): 事件数据对象...args{any[]}: 传递给监听器的额外参数
返回值: {any} - 监听器的返回值,如果默认行为被阻止则返回 false
说明:
- 如果第一个参数是对象且包含
type属性,则作为事件对象 - 事件对象会作为第一个参数传递给所有监听器
- 额外参数会依次传递给监听器
示例:
const eventBus = diagram.get("eventBus");
// 基本用法
eventBus.fire("foo");
// 带数据
eventBus.fire("element.moved", {
element: shape,
delta: { x: 10, y: 20 },
});
// 使用事件对象
const event = { type: "foo", data: "bar" };
eventBus.fire(event);
// 显式指定类型
eventBus.fire("element.updated", { x: 100, y: 200 });
// 传递额外参数
eventBus.fire("custom.event", {}, "param1", "param2");
// 检查默认行为是否被阻止
if (eventBus.fire("element.delete", { element: shape }) === false) {
console.log("删除被阻止");
}createEvent()
作用: 创建一个事件总线可以识别的事件对象。
参数:
data{Object}: 事件数据
返回值: {Event} - 事件对象
示例:
const eventBus = diagram.get("eventBus");
const event = eventBus.createEvent({
type: "custom.event",
element: shape,
data: "some data",
});
// 手动触发事件
eventBus.fire(event);handleError()
作用: 通过触发 error 事件来处理错误。
参数:
error{Error}: 要处理的错误对象
返回值: {boolean} - 如果错误被处理(有监听器阻止了默认行为)返回 true,否则返回 false
说明: 如果没有监听器处理错误,错误会被打印到控制台并重新抛出。
示例:
const eventBus = diagram.get("eventBus");
// 注册错误处理器
eventBus.on("error", function (event) {
console.error("捕获到错误:", event.error);
// 阻止默认行为(避免错误被重新抛出)
return false;
});
// 在监听器中触发错误会被自动处理
eventBus.on("foo", function () {
throw new Error("Something went wrong");
});
eventBus.fire("foo"); // 错误会被捕获并通过 error 事件处理常见事件
diagram-js 中的常见事件类型:
元素事件
element.add/element.added: 元素添加前/后element.remove/element.removed: 元素移除前/后element.changed: 元素发生变化element.updateId: 元素ID更新element.click: 元素被点击element.dblclick: 元素被双击element.hover: 鼠标悬停在元素上element.out: 鼠标离开元素
形状事件
shape.add/shape.added: 形状添加前/后shape.remove/shape.removed: 形状移除前/后shape.move/shape.moved: 形状移动前/后
连接线事件
connection.add/connection.added: 连接线添加前/后connection.remove/connection.removed: 连接线移除前/后
命令事件
commandStack.execute: 命令执行commandStack.changed: 命令栈变化commandStack.reverted: 命令被撤销commandStack.revert: 命令撤销前
画布事件
canvas.viewbox.changed: 视口变化canvas.resized: 画布大小改变diagram.init: 图表初始化diagram.destroy: 图表销毁
事件命名约定
diagram-js 使用以下事件命名约定:
- 前缀事件: 如
element.add,在操作执行前触发,可以被阻止 - 后缀事件: 如
element.added,在操作执行后触发,不可被阻止 - 点分隔: 使用
.分隔命名空间,如canvas.viewbox.changed
优先级说明
- 默认优先级:
1000 - 高优先级:
> 1000(通常用于验证和拦截) - 低优先级:
< 1000(通常用于清理和日志记录) - 最低优先级:
1(用于最后执行的清理操作)
使用建议
- 使用描述性事件名: 使用清晰的命名约定,如
module.action.status - 谨慎使用高优先级: 只在必要时使用高优先级,避免破坏执行顺序
- 及时清理监听器: 组件销毁时使用
off()移除监听器,避免内存泄漏 - 错误处理: 为监听器添加错误处理逻辑,或注册全局
error事件监听器 - 避免在监听器中修改全局状态: 优先使用命令模式进行状态修改
典型用法
监听元素点击
eventBus.on("element.click", function (event) {
const element = event.element;
console.log("点击了元素:", element.id);
});验证操作
// 使用高优先级在操作前进行验证
eventBus.on("shape.move", 1500, function (event) {
const shape = event.shape;
if (!canMove(shape)) {
// 阻止移动
return false;
}
});操作后处理
// 使用低优先级在操作后进行处理
eventBus.on("shape.moved", 500, function (event) {
const shape = event.shape;
console.log("形状已移动到:", shape.x, shape.y);
// 执行清理或更新操作
updateRelatedElements(shape);
});相关模块
CommandStack: 使用 EventBus 触发命令执行事件Canvas: 使用 EventBus 触发画布和元素事件- 所有 Features: 各功能模块都通过 EventBus 进行通信