craft.js 0.1.x 迁移指南:多选(Multiselect)机制下的 EditorState 与节点规则升级
前端【免费下载链接】craft.js A React Framework for building extensible drag and drop page editors项目地址https://gitcode.com/gh_mirrors/cr/craft.js点击查看免费下载导读本文以 craft.js 官方迁移文档 site/docs/migrating/0.1.x.md 为主体系统梳理从 0.1.x 升级到 0.2.x 时由多选Multiselect功能引发的三处破坏性变更EditorState.events从单值NodeId变为集合SetNodeId、基于useEditor的状态收集方式需要改用.has()判断、User Component 的canMoveIn/canMoveOut规则参数从单个Node变为Node[]。阅读本文后你将能够准确识别旧代码中的迁移点并掌握与新事件模型及数组化规则签名对应的完整改造写法。背景为什么 0.2.x 引入了破坏性变更0.1.x 时代的 EditorState 一次只能表达一个“事件目标”当前被选中的节点、被拖拽的节点或被悬停的节点各自都只是一个NodeId。从 0.2.x 开始craft.js 引入了Multiselect多选能力允许用户同时框选多个节点并在一次拖拽中批量移动多个节点。多选使得事件模型必须从“单数”变成“复数”——同一个selected/dragged/hovered事件不再指向唯一节点。这一能力直接体现在核心包的类型定义中EditorEvents被定义为RecordNodeEventTypes, SetNodeIdpackages/core/src/interfaces/editor.ts事件类型仅有三种selected | dragged | hoveredpackages/core/src/interfaces/nodes.tsEditorState通过events: EditorEvents字段挂载这一集合模型packages/core/src/interfaces/editor.ts。因此凡是读取state.events的旧代码都必须同步升级否则类型检查与运行时行为都会出错。变更一EditorState.events 从 NodeId 变为 Set官方迁移文档给出了最直观的类型差异// 0.1.x type EditorState { ... // same as before events: NodeId; } // 0.2.x type EditorState { ... // same as before events: SetNodeId; }对应到当前仓库的源码实现这一签名不仅停留在类型层也贯穿了运行时状态更新逻辑。在 packages/core/src/editor/actions.ts 的setNodeEvent中可以看到先把state.events[eventType]中所有旧节点的事件标记清空state.nodes[id].events[eventType] false将state.events[eventType]重置为new Set()通过getNodesFromSelector解析目标节点支持idOnly/existOnly选项把解析出的 id 列表构造成SetNodeId后写回state.events[eventType]。也就是说events字段在 0.2.x 中始终是Set而不是数组或单个 id。仓库中的测试也验证了这一行为packages/core/src/editor/tests/actions.test.ts 用actions.setNodeEvent(selected, [node-a, node-b])同时选中两个节点并断言期望状态为events: { selected: new Set([node-a, node-b]) }。此外clearEvents()现在会一次性清空三类事件selected、hovered、dragged并重置放置指示器packages/core/src/editor/actions.ts这与集合模型的整体替换语义保持一致。升级要点小结维度0.1.x0.2.xstate.events.selectedNodeId字符串SetNodeId判断某节点是否被选中 node-id.has(node-id)多节点事件表达不支持一个Set同时容纳多个节点 id底层更新方式单值覆盖new Set()整体重建后写入变更二更新 useEditor 收集的状态值由于events变成了Set所有通过useEditor的 collector 函数从state.events收集值的代码都必须从等值比较改为has判断。官方迁移文档给出的对照如下// 0.1.x const { selected, hovered, dragged } useEditor(state ({ selected: state.events.selected some-node-id, hovered: state.events.hovered some-node-id, dragged: state.events.dragged some-node-id, })) // 0.2.x const { selected, hovered, dragged } useEditor(state ({ selected: state.events.selected.has(some-node-id), hovered: state.events.hovered.has(some-node-id), dragged: state.events.dragged.has(some-node-id) }))从源码结构看useEditor的 collector 机制没有变化它接受一个可选的 collector 函数EditorCollectorS内部通过useInternalEditor订阅 store把收集到的切片值作为返回值暴露给组件packages/core/src/hooks/useEditor.tsx。因此迁移成本完全集中在“读取events的方式”上useEditor本身的调用形态、返回的actions/query对象均无需调整。实操检查清单搜索代码中所有state.events.selected 、state.events.hovered 、state.events.dragged 形式的等值比较一律改为state.events.selected.has(...)等.has()调用若某个事件值被当作字符串使用如直接传给 action、拼进 key 等需要重新审视语义0.2.x 下事件值可能是多个节点的集合。变更三canMoveIn / canMoveOut 规则签名升级为 Node[]多选不仅影响状态读取还影响 User Component 的移动规则。在 0.2.x 中canMoveIn与canMoveOut的第一个参数从单个Node变为Node[]因为一次拖拽可能携带多个传入/移出的节点规则必须能对“整批”节点做判断。官方迁移文档给出的对照示例const Button () { return (...) } // 0.1.x Button.craft { rules: { canMoveIn: (incomingNode: Node, currentNode: Node, helpers: NodeHelpers) { return incomingNode.data.name Text; }, canMoveOut: (outgoingNode: Node, currentNode: Node, helpers: NodeHelpers) { return outgoingNode.data.name Text; } } } // 0.2.x Button.craft { rules: { canMoveIn: (incomingNodes: Node[], currentNode: Node, helpers: NodeHelpers) { return incomingNodes.every(incomingNode incomingNode.data.name Text) }, canMoveOut: (outgoingNodes: Node[], currentNOde: Node, helpers: NodeHelpers) { return outgoingNodes.every(outgoingNode outgoingNode.data.name Text) } } }当前仓库的NodeRules类型定义与此完全一致——数组是必选签名export type NodeRules { canDrag(node: Node, helpers: NodeHelpersType): boolean; canDrop(dropTarget: Node, self: Node, helpers: NodeHelpersType): boolean; canMoveIn(canMoveIn: Node[], self: Node, helpers: NodeHelpersType): boolean; canMoveOut(canMoveOut: Node[], self: Node, helpers: NodeHelpersType): boolean; };见 packages/core/src/interfaces/nodes.ts其中NodeHelpersType QueryCallbacksFortypeof QueryMethods[node]即查询方法的 node 维度集合packages/core/src/interfaces/nodes.ts。另外需要注意Button.craft.rules属于可选的PartialNodeRulespackages/core/src/interfaces/nodes.ts并非所有规则都必须实现但一旦实现canMoveIn/canMoveOut就必须使用数组签名。数组参数在底层如何被使用从源码调用链看canMoveIn的数组参数在拖放校验isDroppable中扮演关键角色。在 packages/core/src/editor/NodeHelpers.ts 中isDroppable(selector, onError?)会先把selector解析为一批目标节点newParentNode.rules.canMoveIn( targets.map((selector) selector.node), newParentNode, nodeHelpers )即targets.map(selector selector.node)得到的Node[]会原样传入canMoveIn这意味着若规则仅判断“单个节点是否符合条件”请使用.every()全部满足才放行或按需使用.some()任一满足即可放行多选拖拽时一次isDroppable校验会针对整批节点触发一次canMoveIn而不是逐个触发返回值仍为booleaninvariant会在返回false时抛出ERROR_MOVE_INCOMING_PARENT之类的错误并阻止放置。迁移操作建议先跑类型检查升级到 0.2.x 后先对使用state.events和rules的代码执行 TypeScript 编译编译器会直接标出所有NodeId/Node与SetNodeId/Node[]不兼容的位置这比手工搜索更可靠。集中替换事件读取将useEditor及其他访问state.events的 collector 统一改为.has()判断并注意selected/hovered/dragged三类事件都要覆盖。重写移动规则把canMoveIn/canMoveOut的单节点参数改为数组参数并用every/some表达“整批放行”或“存在即放行”的策略多选拖拽下建议先用every保持与旧行为一致的严格校验。验证多选交互迁移完成后通过actions.setNodeEvent或直接框选多个节点确认state.events.selected为包含多个 id 的Set且拖拽多个节点时canMoveIn收到的是完整的Node[]可参考 packages/core/src/editor/tests/actions.test.ts 的断言写法编写自己的单元测试。小结0.2.x 的多选能力是本次迁移的根本动因它把事件模型从“单节点”提升为“节点集合”相应地带来两处接口级变更events字段与canMoveIn/canMoveOut签名和一处调用级变更collector 改用.has()。按照本文给出的三张对照表与检查清单即可在不改变组件结构的前提下完成平滑迁移并充分使用多选拖拽带来的批量编辑能力。赞分享前端【免费下载链接】craft.js A React Framework for building extensible drag and drop page editors项目地址https://gitcode.com/gh_mirrors/cr/craft.js点击查看免费下载相关推荐VoltAgent 迁移指南从 0.1.x 到 1.x 再到 2.x 的完整升级路径与源码级解析VoltAgent 迁移指南从 0.1.x 到 1.x 再到 2.x 的完整升级路径与源码级解析 本文基于 VoltAgent 仓库中的官方迁移文档 migr人工智能AI AgentAgent 框架后端多智能体RAG工具调用Agent 记忆Agent 工作流AI 评测MCP 服务MCP Clients语音Semantica 跨会话记忆实战用 save() 与 load() 让 Agent 重启不失忆的完整指南Semantica 跨会话记忆实战用 save 与 load 让 Agent 重启不失忆的完整指南 多数 AI Agent 的上下文都活在进程内存里一重启人工智能大模型知识图谱RAGAI 可解释性后端MCP 服务3步解锁数据自由WeChatMsg让你的聊天记录真正属于你3步解锁数据自由WeChatMsg让你的聊天记录真正属于你 你是否曾有过这样的瞬间想找回三年前和好友的那段珍贵对话却发现微信的聊天记录早已消失无踪。那些深上一篇gh_mirrors/c4/c4项目持续部署自动发布与版本管理下一篇3步搞定Yuedu书源误删恢复回收站功能全解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考