ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

XFlow撤销/重做与剪贴板实现原理:changeList命令模式的完整拆解

XFlow撤销/重做与剪贴板实现原理:changeList命令模式的完整拆解 XFlow撤销/重做与剪贴板实现原理changeList命令模式的完整拆解【免费下载链接】XFlowReact component for building interactive diagrams.项目地址: https://gitcode.com/gh_mirrors/xf/XFlowXFlow 是基于 AntV X6 的 React 交互式流程图组件库它的撤销/重做与剪贴板能力由 History、Clipboard 两个插件和一套名为changeList 的命令模式Command Pattern协同实现。本文用通俗语言完整拆解这套机制changeList 是什么、数据如何单向流动、命令栈如何支撑 CtrlZ以及新手如何三步启用这些能力。️ XFlow 撤销重做架构一览插件 命令模式的双层设计XFlow 把「画布」和「数据」分开管理撤销重做与剪贴板正是建立在这个结构之上角色所在文件职责状态仓库 Storepackages/core/src/store/index.ts维护nodes、edges和命令日志changeList分发器 Statepackages/core/src/components/State.tsx把 changeList 里的命令「翻译」成画布操作撤销/重做插件packages/core/src/components/History.tsx挂载 X6 history 插件提供 undo/redo剪贴板插件packages/core/src/components/Clipboard.tsx挂载 X6 clipboard 插件提供 copy/cut/pasteReact 钩子层packages/core/src/hooks/useHistory.ts、useClipboard.ts暴露undo/redo/canUndo/canRedo、copy/cut/paste给业务代码一句话概括changeList 负责「Store → 画布」的状态同步History 命令栈负责「操作的反悔与重演」Clipboard 负责「图形数据的搬运」。三者互相独立、各司其职。 changeList 命令模式详解状态同步的命令日志changeList 的核心思想是每次对数据做修改不只是改数据还要在日志里记一条命令。它的结构极简——每条记录就是一个「命令名 数据」{ command: addNodes, data: [...] }Store 中共定义了 7 种命令覆盖流程图的全部基本操作命令含义记录的数据init初始化整图完整的 nodes edgesaddNodes/removeNodes增删节点节点数组 / 节点 id 列表updateNode修改节点{ id, data }addEdges/removeEdges增删连线连线数组 / 连线 id 列表updateEdge修改连线{ id, data }以addNodes为例action 内部先用 immer 的produce更新nodes自动补 uuid、校验 id 不重复再向changeList压入一条命令而所有 action 都接受一个options.silent开关——静默调用时只改数据、不记日志这是后面避免死循环的关键伏笔。这套设计的收益在于业务代码只需要操作 Store永远不用直接调用 X6 的画布 API「改了什么」全部以命令形式留痕State.tsx中的handleGraphChange再用一个switch把命令逐一「回放」到画布上——init走fromJSON、addNodes走addNodes、updateNode走属性逐路写入……这正是命令模式的经典形态命令即数据执行交给统一分发器。 双向同步如何避免死循环INNER_CALL 标记法changeList 只解决了一半问题用户在画布上直接拖拽、连线时是画布先变、Store 后知后觉。State.tsx同时监听画布事件完成反向同步cell:added/cell:removed画布增删图形 → 以silent: true写回 Storecell:change:*图形属性变化 → 经过 100ms 防抖后合并写回拖拽时不卡顿selection:changed选中态变化 → 同步selected字段。那么问题来了State 把 changeList 的命令应用到画布后画布又会触发cell:added等事件会不会无限循环答案是INNER_CALL标记。所有由 State 自己发起的画布操作都会附带{ __inner__: true }选项事件处理器一看标记就知道这是我自己干的直接跳过回写。一个标记优雅切断了回环。命令批次应用完成后还会通过setTimeout调用clearChangeList清空日志——changeList 是待执行队列不是历史记录用完即清。 撤销/重做History实现原理命令栈与反向命令History /组件本身不渲染任何东西它在挂载时向画布注册 X6 的 history 插件packages/core/src/components/History.tsx。插件的工作方式类似操作系统的剪贴板却是一个命令栈拦截画布的每一类有效变更加节点、拖拽、改属性……把「正向操作 对应的反向操作」成对压入栈中undo()弹出栈顶执行反向操作redo()再把正向操作重放回去。React 侧的useHistorypackages/core/src/hooks/useHistory.ts则把能力包装得恰到好处除了undo/redo它还监听画布的history:change事件实时维护canUndo/canRedo两个状态——你的工具栏按钮能不能点、要不要置灰直接由它驱动。官方示例里还绑定了一组顺手的快捷键metaz撤销、metashiftz重做见apps/basic/src/pages/flow/keyboard.tsx。易混淆点changeList 和 History 是两个独立系统。changeList 是同步完就清空的命令队列History 是越攒越长的可反悔命令栈。前者服务状态同步后者服务撤销重做。 剪贴板Clipboard实现原理复制、剪切与粘贴剪贴板的实现与 History 如出一辙是插件 钩子的组合Clipboard /packages/core/src/components/Clipboard.tsx挂载 X6 clipboard 插件插件内部维护一块剪贴板缓冲记录被复制图形的完整数据含样式与结构useClipboardpackages/core/src/hooks/useClipboard.ts暴露三个方法——copy(ids)把选中的 id 逐个查成画布上的 cell交给graph.copy存入剪贴板cut(ids)复制后再删除原图形paste(options)从剪贴板还原出新图形可传offset让副本错开 48px避免完全重叠。示例中同样绑定了经典快捷键metac复制、metax剪切、metav粘贴apps/basic/src/pages/flow/keyboard.tsx。由于复制的是完整数据而非引用粘贴出来的图形拥有新 id互不干扰也天然支持复制到另一张图的多图玩法。⚡ 三步启用撤销重做与剪贴板开箱即用配置对新手来说全部启用只需要在 JSX 里声明两个组件XFlow XFlowGraph zoomable pannable / Clipboard / History / /XFlow组件即开关渲染了就挂载插件移除了就销毁插件。还有一个贴心的防御机制——useLoadedpackages/core/src/hooks/useLoaded.ts会在调用undo/copy等钩子前检查对应插件是否已挂载若你在页面上忘了写History /控制台会直接提示请先使用 history 组件而不是静默失败。❓ 新手常见疑问 FAQQ1changeList 为什么用完就清空它不是历史吗不是。它是待同步队列命令在State.tsx中执行完、画布已更新后立即清空真正的历史由 History 插件的命令栈负责。Q2我在画布上直接拖了个节点进来为什么 changeList 里没有记录这类画布发起的变更通过事件以silent: true静默写回 Store——数据已同步但不需要再回放给自己自然无需记日志。Q3快捷键撤销不生效怎么排查九成是页面没渲染History /或Clipboard /组件。查看控制台是否有 useLoaded 的告警即可确认。总结XFlow 用 changeList 命令模式实现了 Store 与画布的单向同步与防环回写用 History 插件的命令栈实现撤销/重做用 Clipboard 插件实现图形级剪贴板——三者组合起来让 React 开发者用声明式组件就获得了专业流程图编辑器的全部操作体验。若想动手验证可直接参考示例代码apps/basic/src/pages/flow/与apps/basic/src/pages/dag/toolbar/中的完整用法。【免费下载链接】XFlowReact component for building interactive diagrams.项目地址: https://gitcode.com/gh_mirrors/xf/XFlow创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表