资讯动态

LogicFlow 事件机制完全指南:eventCenter 通讯中心、事件命名规范与源码级实现解析

发布时间:2026/9/15 12:57:43 来源:尧图企业网站定制
LogicFlow 事件机制完全指南eventCenter 通讯中心、事件命名规范与源码级实现解析【免费下载链接】LogicFlowA flow chart editing framework focus on business customization. 专注于业务自定义的流程图编辑框架支持实现脑图、ER图、UML、工作流等各种图编辑场景。项目地址: https://gitcode.com/GitHub_Trending/lo/LogicFloweventCenter是 LogicFlow 内部的通讯中心负责以低耦合方式连接图编辑器的各个模块节点、边、锚点、画布、历史记录与选区等通过它发布或监听事件业务侧则通过lf.on()/lf.off()等实例方法订阅这些事件。本文以 packages/core/src/event/event.md 为骨架结合 eventEmitter.ts、eventArgs.ts 与各视图层源码系统讲解事件命名规范、事件对象结构、发布/订阅 API 及其底层实现帮助你在基于 LogicFlow 的业务开发中正确使用事件系统并能在需要时写出符合框架规范的自定义事件。一、eventCenter 是什么低耦合的模块间通讯中枢eventCenter是 LogicFlow 内部的事件总线Event Bus。在流程图的复杂交互场景中节点点击、边删除、画布平移等行为往往涉及多个模块联动如果模块之间直接互相引用会形成强耦合、难以维护。LogicFlow 的解法是所有内部模块统一通过eventCenter发布emit事件关心某个事件的模块或业务用户通过eventCenter监听on事件发布方与监听方互不感知对方的存在只依赖事件名这一约定完成通信。从源码结构看GraphModel.ts 在构造函数中创建了eventCenter new EventEmitter()实例并将其挂载为graphModel.eventCenterLogicFlow.tsx 在初始化时取出this.graphModel.eventCenter用于驱动历史记录History、吸附线snapline、键盘Keyboard等内置能力。也就是说整个画布从创建那一刻起所有模块共享同一个事件中心实例。在开发过程中使用eventCenter也需要遵循一些规范下文逐一展开。二、事件命名规范namespace:eventName为什么需要 namespaceLogicFlow 规定事件命名遵循namespace:eventName的结构同类eventName通过 namespace 来区分。例如node和edge都会抛出click事件但观察者可能是不同群体——节点被点击与边被点击的业务含义完全不同。通过 namespace 前缀事件监听变得更精确不容易串扰、不容易出错。以node:click和edge:click为例node:click仅在节点被点击时触发edge:click仅在边被点击时触发。如果业务只关心节点点击只需lf.on(node:click, ...)完全不会收到边的点击通知。core 包中已定义的 namespace 全集原文档列出了 core 包中定义的核心 namespacenode节点事件、edge边事件、anchor锚点事件、blank画布空白区域事件、history历史记录事件、selection选区事件。从 constant/index.ts 的EventType枚举可以看到实际事件体系比这更完整除了上述 6 类外还包括namespace含义代表事件EventType 常量node节点事件node:click、node:dbclick、node:drag、node:resize、node:rotate、node:properties-change等edge边事件edge:click、edge:dbclick、edge:adjust、edge:exchange-node等anchor锚点事件anchor:click、anchor:dragstart、anchor:drop、anchor:dragend等blank画布空白区域事件blank:mousedown、blank:click、blank:contextmenu等history历史记录事件history:changeselection选区事件selection:mousedown、selection:drag、selection:contextmenu等text文本事件text:click、text:update、text:clear等labellabel 插件文本事件label:click、label:should-add、label:batch-add等element元素公共事件element:clicknode:click与edge:click的并集graph画布事件graph:transform、graph:rendered、graph:updated、graph:resizeconnection连线校验事件connection:not-allowedadjustPoint折线调整点事件adjustPoint:mousedown、adjustPoint:drag等提示事件字符串常量集中在EventType枚举中开发插件或深入源码时优先引用枚举而不是手写字符串可避免拼写错误。三、事件发布emit的两种途径与组件级规范原文档明确了内部抛出事件的两种方式这在阅读源码时可以得到完整印证途径一graphModel 中通过this.eventCenter抛出graphModel自身持有eventCenter实例需要广播模型状态变化时直接调用其emit方法。例如节点删除GraphModel.tsthis.eventCenter.emit(EventType.NODE_DELETE, { data: nodeData })边的新增、删除同理GraphModel.ts、GraphModel.ts节点properties变化则由 BaseNodeModel.ts 通过this.graphModel.eventCenter.emit(EventType.NODE_PROPERTIES_CHANGE, ...)抛出。途径二组件通过 props 获取 eventCenter 后抛出视图组件有两类获取方式原文档明确指出部分组件直接从props中获取eventCenter实例。例如折线边组件 PolylineEdge.tsx 中直接eventCenter.emit(EventType.EDGE_ADJUST, { data: polylineModel.getData() })。另一部分组件从props中获取graphModel再通过graphModel.eventCenter拿到实例。例如锚点组件 Anchor.tsx 与节点组件 BaseNode.tsx// BaseNode.tsx 中抛出节点单击 / 双击事件 graphModel.eventCenter.emit(EventType.NODE_DBCLICK, eventOptions) graphModel.eventCenter.emit(EventType.NODE_CLICK, eventOptions)画布空白区域的blank:*事件则在 CanvasOverlay.tsx 中抛出例如blank:click、blank:contextmenu、blank:mousedown。组件生命周期规范销毁时必须取消监听原文档特别强调如果组件内部监听了eventCenter事件在组件销毁的时候必须取消这些监听。这背后是内存泄漏问题——事件总线持有回调引用若组件卸载后监听未移除回调仍会被触发并持有组件上下文。规范做法是componentDidMount() { this.eventCenter.on(node:click, this.handleNodeClick) } componentWillUnmount() { this.eventCenter.off(node:click, this.handleNodeClick) }四、事件对象emit 的第二参数即回调入参在使用emit方法抛出事件时可以传递一个对象作为第二个参数该对象将作为对应事件监听器回调函数的入参。事件对象可以包含任何与当前事件相关的信息比如节点的 id、边的 id、原生鼠标事件对象等。原文档以node:click事件为例给出了完整的发布与监听代码// 抛出 node:click 事件 eventCenter.emit(node:click, { data: { // 节点数据 }, e: MouseEvent, position: { // 鼠标点击的位置信息 } }); // 监听 node:click 事件 eventCenter.on(node:click, (event) { console.log(event); // event 即为抛出事件时传递的对象 }); // 使用解构赋值可以便捷地获取事件对象中的信息 eventCenter.on(node:click, ({ data, e, position }) { console.log(data, e, position); });事件对象的 TypeScript 类型定义从源码结构看core 包在 eventArgs.ts 中为所有内置事件定义了严格的参数类型聚合导出为EventArgs。以节点事件为例其参数通过NodeEventArgsPick按事件场景按需选取node:click/node:dbclick/node:contextmenu{ data, e, position }其中position是鼠标触发点相对画布左上角的坐标ClientPositionnode:mousedown/node:mouseup/node:mouseenter/node:mouseleave{ data, e }node:drag/node:mousemove额外包含deltaX、deltaY鼠标在 X/Y 轴移动的距离node:resize额外包含preData上一个状态的节点数据、model以及indexResize 时调整的是哪个控制点node:click还额外混入了ClickEventArgs的isSelected点击后节点是否处于选中状态与isMultiple是否为多选状态。其它事件族同理边事件EdgeEventArgsedge:click等为{ data, e, position }edge:exchange-node的参数为{ data: { newEdge, oldEdge } }锚点事件AnchorEventArgs参数包含data锚点配置、e、nodeModel锚点所属节点anchor:drop与anchor:dragend在成功连线时还会携带edgeModel画布事件BlankEventArgs参数为{ e, position }选区事件SelectionEventArgsselection:contextmenu参数为{ data, e, position }公共事件CommonEventArgselement:click为{ data, e, position }graph:transform为{ type, transform }graph:rendered为{ data, graphModel }历史事件HistoryEventArgshistory:change的参数为{ data: { undos, redos, undoAble, redoAble } }。值得说明的是CallbackArgsT的机制是如果事件名不是内部定义的事件类型那么允许用户抛出任何类型的参数类型由用户自己保证见 eventEmitter.ts。这意味着你可以自由定义自己的自定义事件并在回调中接收任意结构的数据。五、发布/订阅 API 详解与底层实现eventCenter的核心实现是 eventEmitter.ts 中的EventEmitter类同时导出了EventArgs类型。它提供四个核心方法与若干辅助能力并有对应单元测试验证见 event/event.test.ts。5.1 on注册监听on(evt: string, callback: EventCallback, once?: boolean): voidevt支持用逗号分隔的多个事件名内部会逐个拆分并 trim 后注册evt?.split(,).forEach(...)同一事件可注册多个回调按注册顺序依次触发once参数为true时回调触发一次后自动移除once()方法即this.on(evKey, callback, true)的语法糖。5.2 emit触发事件emit(evts: string, eventArgs?: EventCallback): voidemit同样支持逗号分隔的多个事件名。它的执行流程包含一个值得注意的细节每个事件触发时除了执行该事件自身的回调列表还会执行通配符事件*下的所有回调const events this._events[evt] || [] const wildcardEvents this._events[WILDCARD] || [] // WILDCARD * doEmit(events) doEmit(wildcardEvents)这意味着你可以通过eventCenter.on(*, callback)监听画布上的所有事件这对调试、埋点统计等场景非常有用。同时once监听在触发时会被就地移除es.splice(i, 1)并同步修正遍历长度。5.3 off取消监听off的语义分三种情况源码注释明确说明evts为空清除所有事件的监听器this._events {}evts非空、callback为空清除指定事件的所有监听器delete this._events[evt]evts非空、callback非空通过对象引用比较events[i].callback callback清除指定事件的指定监听器事件监听器被清空后该事件键也会被删除。// 用法示例 eventCenter.off(node:click) // 移除 node:click 的所有监听 eventCenter.off(node:click, handler) // 仅移除 handler 这个监听 eventCenter.off() // 移除全部监听5.4 once只监听一次once(evt: string, callback: EventCallback): void注册后首次触发即自动移除等价于on(evt, callback, true)。注意once注册的监听在触发一次后若没有其它监听残留对应事件键会从_events中删除测试用例 event.test.ts 验证了这一行为。5.5 辅助方法getEvents 与 destroygetEvents()返回当前所有事件及其回调列表this._events可用于调试destroy()清空全部事件监听this._events {}GraphModel.ts 在销毁流程中调用它完成事件系统的资源释放。六、业务侧如何使用lf 实例的 on/off/once/emit普通用户并不需要直接接触eventCenter实例。LogicFlow 实例lf已经将graphModel.eventCenter的四个核心方法透传为实例方法LogicFlow.tsxlf.on(node:click, ({ data, e, position }) { ... }) lf.off(node:click, handler) lf.once(graph:rendered, ({ data, graphModel }) { ... }) lf.emit(custom:event, { foo: bar })也就是说lf.on(evt, callback)内部等价于this.graphModel.eventCenter.on(evt, callback)。用户可以通过lf监听框架抛出的所有内置事件也可以通过lf.emit向eventCenter广播自定义事件实现与插件、与其他模块之间的解耦通信。典型实践示例监听节点点击与画布渲染// 节点点击事件对象解构出 data / e / position lf.on(node:click, ({ data, e, position }) { console.log(点击了节点, data.id, 坐标, position.x, position.y) }) // 画布渲染完成lf.render() 被调用后触发 lf.once(graph:rendered, ({ data, graphModel }) { console.log(首次渲染完成共, graphModel.nodes.length, 个节点) }) // 画布尺寸变化已做 16ms 防抖 lf.on(graph:resize, ({ contentRect }) { console.log(画布新尺寸, contentRect.width, contentRect.height) })关于graph:updated的注意事项eventArgs.ts 中的注释对graph:updated给出了明确建议该事件在lf.render(graphData)被调用后、或改变画布graphModel上的属性后都会触发。如果你只是想在某次主动修改后执行一次操作建议注册事件后在回调中及时注销该事件或者使用once代替on因为其它属性变更也可能触发该事件导致回调被意外多次执行。七、源码佐证一条事件从抛出到监听的完整链路以node:click为例梳理完整的事件流以源码为准抛出端用户在画布上点击节点节点视图组件 BaseNode.tsx 捕获原生点击事件后调用graphModel.eventCenter.emit(EventType.NODE_CLICK, eventOptions)其中eventOptions即{ data, e, position }结构总线EventEmitter.emit根据事件名node:click找到回调列表逐个调用回调并将once监听移除随后执行通配符*监听监听端业务侧lf.on(node:click, callback)注册的回调被调用入参即eventOptions。整条链路中抛出端不关心谁在监听监听端不关心事件从哪来双方只依赖node:click这个命名约定——这正是eventCenter低耦合设计的价值所在。八、总结与开发规范速查关注点规范事件命名一律使用namespace:eventName结构core 包事件常量见 constant/index.ts 的EventType事件参数emit的第二个参数即回调入参内置事件参数类型见 eventArgs.ts 的EventArgs发布方式模块/组件内统一通过eventCenter或graphModel.eventCenter调用emit订阅方式业务侧使用lf.on/lf.off/lf.once与eventCenter方法一一对应组件生命周期组件销毁时必须off掉自己注册的监听避免内存泄漏单次监听用once或监听到目标后立即off尤其注意graph:updated的触发条件全局调试eventCenter.on(*, ...)可监听所有事件getEvents()可查看当前全部监听掌握这套事件机制你就能精准订阅节点、边、锚点、画布、历史、选区等各类交互也能基于lf.emit构建属于自己的模块间通信协议在业务自定义的道路上进一步发挥 LogicFlow 框架的扩展能力。【免费下载链接】LogicFlowA flow chart editing framework focus on business customization. 专注于业务自定义的流程图编辑框架支持实现脑图、ER图、UML、工作流等各种图编辑场景。项目地址: https://gitcode.com/GitHub_Trending/lo/LogicFlow创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价