- 前端
- UI组件
【免费下载链接】vue-flow
A highly customizable Flowchart component for Vue 3. Features seamless zoom & pan 🔎, additional components like a Minimap 🗺 and utilities to interact with state and graph.
本文是 Vue Flow(@vue-flow/core)事件系统的完整技术指南。VueFlow 围绕画布运行时的每一个关键动作——节点点击与拖拽、边的连接与更新、视口平移缩放、选区变化、画布初始化等——都提供了可订阅的事件,你可以通过组件上的@指令或useVueFlow()返回的on<EventName>Hooks 两种模式接收通知并驱动业务逻辑。读完本文,你将掌握全部事件的名称、参数结构、订阅与退订方式,以及事件系统在源码层面的触发机制,能够基于事件构建交互式、响应式的流程图应用。
事件系统总览:VueFlow 能告诉你什么
事件是 VueFlow 与应用代码之间的通信桥梁。在 events.md 中明确说明:VueFlow 提供了一组事件,你可以监听它们来响应流程中的变化("react to changes in the flow")。所有可用事件的完整清单定义在核心包的类型文件中。
从源码看,事件全集由FlowEvents接口统一描述,见 packages/core/src/types/hooks.ts。同时组件侧的defineEmits由 packages/core/src/types/flow.ts 中的FlowEmits接口约束,二者覆盖的事件一一对应。按功能域划分,这些事件可以归纳为以下八类:
| 类别 | 事件(kebab-case / camelCase) | 触发时机 |
|---|---|---|
| 节点交互 | node-click、node-double-click、node-mouse-enter、node-mouse-move、node-mouse-leave、node-context-menu | 鼠标在节点上的单击、双击、悬停、移动、离开、右键 |
| 节点拖拽 | node-drag-start、node-drag、node-drag-stop | 节点拖拽开始、进行中、结束 |
| 节点状态 | nodes-change、nodes-initialized、update-node-internals | 节点集合变更、全部节点初始化完成、节点内部尺寸等需要更新时 |
| 边相关 | edge-click、edge-double-click、edge-mouse-enter、edge-mouse-move、edge-mouse-leave、edge-context-menu、edge-update-start、edge-update、edge-update-end、edges-change | 边上的鼠标事件,以及拖拽边端点更新连接的全过程 |
| 连接 | connect-start、connect、connect-end、click-connect-start、click-connect-end | 从 Handle 拖出连线开始/完成/结束,以及connectOnClick点击连线的全过程 |
| 视口与画布 | move-start、move、move-end、viewport-change-start、viewport-change、viewport-change-end、pane-scroll、pane-click、pane-context-menu、pane-mouse-enter、pane-mouse-move、pane-mouse-leave | 平移缩放、视口变换、画布空白区域的滚轮/点击/右键/鼠标行为 |
| 选区 | selection-start、selection-end、selection-drag-start、selection-drag、selection-drag-stop、selection-context-menu | 框选开始/结束及多选拖拽 |
| 生命周期与错误 | init(pane-ready已废弃) | 画布初始化完成、发生错误时抛出error |
其中需要留意两个历史沿革点:
pane-ready已废弃,官方以init替代,二者回调参数均为VueFlowStore(即useVueFlow()返回的 store 实例);nodesChange与edgesChange是两个"数据驱动"的事件:当nodes/edges数据发生增删改(含拖拽导致的 position 变化)时,会以变更描述(NodeChange[]/EdgeChange[])的形式触发,是受控模式下同步外部状态的核心入口。
方式一:在 VueFlow 组件上用@指令监听
最直接的方式是在<VueFlow>组件上通过 Vue 的事件绑定语法监听事件,事件名采用 kebab-case(短横线命名):
<script setup> import { ref } from 'vue'; import { VueFlow } from '@vue-flow/core'; const nodes = ref([/* ... */]); const edges = ref([/* ... */]); // 节点点击事件处理函数 function onNodeClick({ event, node }) { console.log('Node clicked:', node, event); } // 边点击事件处理函数 function onEdgeClick({ event, edge }) { console.log('Edge clicked:', edge, event); } </script> <template> <VueFlow :nodes="nodes" :edges="edges" @node-click="onNodeClick" @edge-click="onEdgeClick"></VueFlow> </template>要点说明:
- 组件事件名全部使用 kebab-case:
@node-click、@edge-click、@connect、@move、@node-drag等; - 回调参数即事件载荷对象(下文详解);
- 这一机制之所以能工作,是因为 VueFlow.vue 通过
defineEmits<FlowEmits>()声明了全部事件,模板上的监听器会经 Vue 的 props 继承机制传递到组件实例上。
方式二:通过useVueFlow()的事件 Hooks 监听
第二种方式是在脚本中以编程方式订阅:useVueFlow()返回的 Flow 实例(store)暴露了全部事件 Hook,命名规则为on<EventName>——例如node-click对应onNodeClick,connect对应onConnect。
<script setup> import { ref } from 'vue'; import { VueFlow, useVueFlow } from '@vue-flow/core'; const nodes = ref([/* ... */]); const edges = ref([/* ... */]); // 所有事件都可以从 `useVueFlow` 中以 `on<EventName>` 的形式获取 const { onNodeClick, onEdgeClick } = useVueFlow(); // 节点点击事件处理函数 onNodeClick(({ event, node }) => { console.log('Node clicked:', node, event); }); // 边点击事件处理函数 onEdgeClick(({ event, edge }) => { console.log('Edge clicked:', edge, event); }); </script> <template> <VueFlow :nodes="nodes" :edges="edges" @node-click="onNodeClick" @edge-click="onEdgeClick"></VueFlow> </template>Hooks 模式的核心优势:
- 不依赖模板:可以在任意组合式函数、Pinia store、独立工具模块中订阅事件,无需组件层级传递;
- 更精细的控制粒度:Hook 订阅支持返回值
{ off: () => void }手动退订;同时底层基于tryOnScopeDispose自动在作用域销毁时清理,见 createExtendedEventHook.ts; - 天然支持组合式 API:
useVueFlow()可在任何 Setup 上下文调用。从 useVueFlow.ts 的实现看,它会优先从 Vue 的注入上下文(inject(VueFlow, ...))获取已有 store;找不到时则通过内部 Storage 按 id 获取或新建一个 store 实例。传入 id 或 options 对象可以精确控制要订阅的 store 实例。
值得注意的是,两种方式可以共存:组件上写@node-click、同时又在脚本里onNodeClick(...),两个监听器都会触发,互不覆盖(底层实现见下文"触发顺序")。
事件载荷:回调参数的结构与类型
每个事件的回调参数都由类型定义严格约束。核心的事件载荷类型集中定义在 packages/core/src/types/hooks.ts:
| 载荷类型 | 结构 | 对应事件 |
|---|---|---|
NodeMouseEvent | { event: MouseTouchEvent; node: GraphNode } | node-click、node-double-click、node-mouse-*、node-context-menu、mini-map-node-* |
NodeDragEvent | { event; node: GraphNode; nodes: GraphNode[] } | node-drag-start/node-drag/node-drag-stop、selection-drag-* |
EdgeMouseEvent | { event; edge: GraphEdge } | edge-click、edge-double-click、edge-mouse-*、edge-context-menu、edge-update-start、edge-update-end |
EdgeUpdateEvent | { event; edge: GraphEdge; connection: Connection } | edge-update(拖拽更新边端点过程中持续触发) |
Connection | 见packages/core/src/types/connection.ts | connect(含source、target、sourceHandle、targetHandle等字段) |
ViewportTransform | 见packages/core/src/types/zoom.ts | viewport-change-*(含x、y、zoom) |
VueFlowStore | store 实例 | init、pane-ready(废弃) |
VueFlowError | 错误对象(含code与message) | error |
其中MouseTouchEvent是MouseEvent | TouchEvent的联合类型,说明事件同时兼容鼠标与触摸设备,可放心用于触屏场景。
源码揭秘:事件是如何从触发点传到监听器的
理解底层实现有助于排查问题与设计复杂交互。事件链路共三层:
第一层:Hook 工厂创建。packages/core/src/store/hooks.ts 中的createHooks()为FlowEvents中的每一个事件创建对应的EventHookExtended实例。注意errorHook 自带一个默认处理器:createExtendedEventHook((err) => warn(err.message)),即未订阅错误事件时,内部错误至少会以警告形式打印,避免静默失败。
第二层:与组件 emit 桥接。同一文件中的useHooks(emit, hooks)(store/hooks.ts)在onBeforeMount阶段为每个 Hook 调用setEmitter(listener),把组件emit注册为外部发射器——这正是组件上@node-click等模板监听能收到通知的原因。同时它还通过setHasEmitListeners检测 vnode props 上是否存在onNodeClick之类的监听器,见 VueFlow.vue 对useHooks(emit, vfInstance.hooks)的调用。
第三层:触发与分发。事件触发点(如节点组件、边组件、Pane 的拖拽/缩放逻辑)调用 Hook 的trigger(param)。其核心实现在 createExtendedEventHook.ts,触发顺序为:
- 先调用外部发射器(
emitter,即组件 emit); - 若存在用户通过
on()注册的监听器,则继续调用全部监听器;否则(没有任何监听器时)回退执行默认处理器(目前仅error提供了默认处理器); - 所有处理器通过
Promise.allSettled并行执行——单个监听器抛错不会影响其他监听器与后续流程,错误被隔离处理。
这一设计同时保证了"模板监听 + Hooks 订阅共存"以及"监听器异常不影响画布主流程"的健壮性。
实战:基于事件的常见交互模式
1. 连接事件链:完成连线并在创建时更新数据
参考测试 connect.cy.ts,一次完整的拖拽连线会依次触发connect-start→connect→connect-end:
const { onConnectStart, onConnect, onConnectEnd } = useVueFlow() onConnectStart(() => console.log('开始连线')) onConnect((connection) => { console.log('连线完成:', connection.source, connection.target) // 在此处校验或持久化 connection }) onConnectEnd(() => console.log('连线结束'))若需要限制可连接的目标节点,可使用isValidConnection选项配合connect事件做二次校验;示例文档 validation.md 展示了通过onConnect事件结合校验逻辑拒绝非法连线的用法。
2. 拖拽更新边的端点:onEdgeUpdate
自定义边支持端点拖拽(updatable)时,更新过程会产生edge-update-start、edge-update、edge-update-end三个事件。标准写法是用onEdgeUpdate把新的connection应用回 store:
// 参见测试 updateEdge.cy.ts 与 isValidConnection.cy.ts 的用法 const { onEdgeUpdate, updateEdge } = useVueFlow() onEdgeUpdate(({ edge, connection }) => updateEdge(edge, connection))参考实现见 tests/cypress/component/2-vue-flow/updateEdge.cy.ts 与 tests/cypress/component/2-vue-flow/isValidConnection.cy.ts。
3. 同步外部状态:onNodesChange与onEdgesChange
在受控模式下(将nodes/edges作为 props 传入),需要把画布内部产生的变更同步回自己的状态。官方推荐用 Hooks 订阅:
const { onNodesChange, onEdgesChange, applyNodeChanges, applyEdgeChanges } = useVueFlow() onNodesChange((changes) => (nodes.value = applyNodeChanges(changes))) onEdgesChange((changes) => (edges.value = applyEdgeChanges(changes)))值得一提的是,useVueFlow在创建 store 时会根据applyDefault选项自动注册内置的nodesChange/edgesChange处理器(调用applyNodeChanges/applyEdgeChanges),见 useVueFlow.ts。这解释了为什么默认情况下@nodes-change未被监听时节点拖拽也能正常移动——默认处理器兜底保证了开箱即用的行为。
4. 视口状态与初始化
const { onInit, onMove, onViewportChange } = useVueFlow() onInit((instance) => console.log('画布已初始化', instance.getNodes())) onMove(({ flowTransform }) => console.log('平移/缩放中:', flowTransform)) onViewportChange((viewport) => console.log('视口变化:', viewport))init事件在画布挂载完成后触发,回调参数即 store 实例,可立即调用getNodes()、fitView()等 API;move系列在每次平移缩放时高频触发,适合做同步侧边栏坐标、保存视口位置等场景。
事件与自定义事件扩展
除了内置事件,VueFlow 的类型系统还预留了自定义事件能力:CustomEvent<Args, Return>与NodeEventsHandler/EdgeEventsHandler类型(见 hooks.ts)允许你基于 Hook 机制为自定义节点和边组件扩展事件处理器(click、dragStart、updateStart等),并将自定义事件并入类型推导。需要构建高度定制化节点/边交互时,这是从类型层面扩展事件体系的标准入口。
事件订阅的正确姿势与注意事项
- kebab-case 与 camelCase 的对应关系:模板上用
@node-click,Hook 上用onNodeClick,二者指向同一个事件;事件名的大小写转换逻辑可参考 store/hooks.ts 中的toHandlerKey实现。 - 监听器的生命周期:Hook 订阅默认跟随当前 Setup 作用域自动销毁(
tryOnScopeDispose);若需提前取消,保存on()返回的{ off }并调用off()即可。 - 不要为高频事件做重逻辑:
node-mouse-move、move、node-drag触发频率极高,回调内应避免昂贵的计算或 DOM 操作,必要时自行节流/防抖。 nodesChange/edgesChange的参数是变更描述数组,不是完整的新数组;如需获取应用后的完整数据,结合applyNodeChanges/applyEdgeChanges处理(即受控模式的标准流程)。- 测试验证:仓库的 Cypress 组件测试覆盖了多条事件链路,如 connect.cy.ts 验证了
onConnectStart/onConnect/onConnectEnd各精确触发一次,可作为你编写事件驱动逻辑与回归测试的参照模板。
结语
VueFlow 的事件系统覆盖了节点、边、连接、视口、选区与生命周期全部关键动作,且同时提供组件模板监听(@指令)与useVueFlowHooks(on<EventName>)两套等价入口。理解 FlowEvents 的事件全集与 createExtendedEventHook 的分发机制后,你既能通过事件驱动业务状态同步,也能在组合式 API 场景下保持代码的模块化与可测试性。更完整的事件列表与参数类型,可查阅核心包类型定义与组件FlowEmits声明(flow.ts)。
- 前端
- UI组件
【免费下载链接】vue-flow
A highly customizable Flowchart component for Vue 3. Features seamless zoom & pan 🔎, additional components like a Minimap 🗺 and utilities to interact with state and graph.
相关推荐
LogicFlow 事件系统完全指南:从事件监听到自定义事件实战
LogicFlow 事件系统完全指南:从事件监听到自定义事件实战 导读 本文以 LogicFlow 官方基础教程《Event》为核心,系统讲解流程图编辑器的事件
前端低代码流程编排G6 事件系统完全指南:事件监听 API 与常量枚举实战
G6 事件系统完全指南:事件监听 API 与常量枚举实战 G6 为 JavaScript 图可视化应用提供了完整的事件机制,支持响应节点点击、边悬停、画布拖拽等
数据可视化前端图表库nodemon 事件系统完全指南:从事件模型、进程通信到实战监听
nodemon 事件系统完全指南:从事件模型、进程通信到实战监听 nodemon 在监听文件变化的同时,会围绕子进程的生命周期发射一系列事件,本文以仓库 doc
开发工具CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考