ARTICLE DETAIL

资讯详情

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

craft.js 中 `<Element />` 组件的完整指南:定义节点、构建 Canvas 与创建 Linked Nodes

craft.js 中 `<Element />` 组件的完整指南:定义节点、构建 Canvas 与创建 Linked Nodes 前端【免费下载链接】craft.js A React Framework for building extensible drag and drop page editors项目地址https://gitcode.com/gh_mirrors/cr/craft.js点击查看免费下载Element /是 craft.js一个用于构建可扩展拖拽式页面编辑器的 React 框架中定义编辑器 Node 的核心组件。本文以官方 API 文档 Element.md 为主体深入讲解Element /的全部 Props、在Frame /与 User Component 中的两种使用场景、Linked Nodes 的创建机制并结合packages/core源码揭示其底层实现原理与工程实践帮助你用最小的成本搭建出可拖拽、可配置的页面编辑器。Element /是什么在 craft.js 中编辑器的一切内容都由Node表示每个可拖拽、可配置的组件都对应一个 NodeNode 之间以树形结构组织。Element /就是用来定义某个用户元素的 Node的组件——它本身不直接渲染业务 UI而是把 JSX 描述转换为编辑器内部的状态树NodeTree。从源码看Element /位于 packages/core/src/nodes/Element.tsx其核心逻辑非常简洁读取当前 Node 上下文后在两种模式下工作当它被用作Frame /的直接子元素时它只是配置那个已经被 Frame 自动创建的 Node当它被用在 User Component 内部时由于原位没有 Node它会新建一个 Linked Node——一个通过任意id与宿主 User Component 的 Node 建立关联的独立 Node。这正是Element /与普通 React 组件最大的区别它是一个 Node 的声明与配置入口而非单纯的 UI 容器。Props 参考Element /接受的 Props 定义在 packages/core/src/nodes/Element.tsx 的ElementProps类型中完整列表如下Prop类型说明isReact.ElementType要渲染的用户元素类型。默认值为dividString当Element /在 User Component 内部创建时必须指定用于创建 Linked Nodecanvasboolean若为true将创建一个 Canvas Node即可拖拽又可作为拖放目标customRecordstring, any设置 Node 的custom属性可被 User Component 当作附加 props 消费hiddenboolean设置 Node 的hidden属性为true时隐藏该 Node...elementPropsObject透传给is指定元素的其他 props其中is、canvas、custom、hidden的默认值在源码中明确给出// packages/core/src/nodes/Element.tsx export const defaultElementProps { is: div, canvas: false, custom: {}, hidden: false, };另外源码中还有一个易被忽略的映射关系——elementPropToNodeData它定义了is与canvas两个 prop 与 Node 内部数据字段的对应// packages/core/src/nodes/Element.tsx export const elementPropToNodeData { is: type, // 决定 Node 的 type canvas: isCanvas, // 决定 Node 是否是一个 Canvas 节点 };也就是说is与canvas本质上是 Node 数据NodeData中type与isCanvas字段的 JSX 层快捷写法。完整字段可见 packages/core/src/interfaces/nodes.ts 中的NodeData定义props、type、name、displayName、isCanvas、parent、linkedNodes、nodes、hidden、custom等。在Frame /中配置 NodeFrame /是编辑器可编辑区域的入口组件。它会对所有子元素自动创建 Node因此当Element /作为 Frame 的子元素出现时它的作用只是配置这些将被创建的 Node 的值而不是新建 Node。这一点在 Frame.tsx 源码中得到印证Frame会取唯一的根子元素通过query.parseReactElement(rootNode).toNodeTree(...)将 JSX 解析成 NodeTree并把根节点 id 规范化为ROOT_NODE见 constants.ts 中的ROOT_NODE ROOT最后通过RenderRootNode渲染。文档中的经典示例完整展示了三种节点的差异import {Craft, Frame, Element} from craftjs/core; const App () { return ( div h2My App!/h2 Craft resolver{{MyComp}} h2My Page Editor/h2 Frame Element isdiv canvas // 定义根 Node可拖放 h2Drag me around/h2 // type 为 h2 的 Node可拖拽 MyComp textYou can drag me around too / // type 为 MyComp 的 Node可拖拽 Element isdiv style{{background: #333 }} canvas // type 为 div 的 Canvas Node可拖拽且可拖放 pSame here/p // 不是 Node不可拖拽 /Element /Element /Frame /Craft /div ) }结合上面的例子可以总结出以下规律作为根节点的Element isdiv canvas定义了整个编辑区的根 NodeROOT它是一个 Canvas 节点作为拖放目标h2、MyComp等 JSX 会被自动解析为对应 type 的 Node默认可拖拽嵌套的Element isdiv style{{background: #333}} canvas会创建一个新的 Canvas Node——style等其余 props 会透传给内部的div元素最内层的p只是普通 DOM 元素由于它不是用户组件也没有被Element包裹不会成为 Node因此不可拖拽。关于canvas属性的补充旧版本中对应的Canvas /组件已被标记为废弃。在 Canvas.tsx 中可以看到它只是简单地渲染Element {...props} canvas{true} /并打印弃用提示suggestElement canvas{true} /。因此新代码请统一使用Element canvas /写法。在 User Component 中定义 Linked Nodes当Element /用在 User Component用户自定义组件内部时情况变得不同此时原位并没有已存在的 Node所以Element /必须新建一个 Linked Node。Linked Node 本质上是一个通过任意id与包含它的 User Component 的 Node关联起来的新 Node。const Hero () { return ( div h3Im a Hero/h3 Element iddrop is{Container} canvas h3Hi/h3 /Element /div ) }⚠️在 User Component 内部使用Element /必须指定idprop。这条约束并非口头约定而是有运行时断言保证的在 Element.tsx 中Element初始化时会执行invariant(!!id, ERROR_TOP_LEVEL_ELEMENT_NO_ID)对应的错误信息定义在 constants.tsA Element / that is used inside a User Component must specify an id prop, eg: Element idtext_element.../ElementLinked Node 的创建流程从源码看Element /在 User Component 内部渲染时会走如下逻辑Element.tsx通过useInternalNode()拿到当前所在 Node 的nodeId与inNodeContext检查宿主 Node 的data.linkedNodes[id]是否已存在且 type 与 JSX 中的is一致——若一致则直接渲染这个已有的 Linked Node保证幂等避免重复创建否则把Element的 JSX 通过query.parseReactElement(...).toNodeTree()解析为 NodeTree调用actions.history.ignore().addLinkedNodeFromTree(tree, nodeId, id)以跳过历史记录的方式history.ignore()把新的 NodeTree 挂载到宿主 Node 的linkedNodes[id]上最后渲染NodeElement id{linkedNodeId} /见 NodeElement.tsx它内部通过RenderNodeToElement真正输出组件。addLinkedNodeFromTree是 Editor 内部方法源码中明确注明Only used internally by the component定义于 actions.ts。它会先校验宿主 Node 存在若linkedNodes[id]已有旧节点则先递归删除再通过addNodeTreeToParent(tree, parentId, { type: linked, id })把新树的根节点 id 写入parent.data.linkedNodes[id]见 actions.ts。数据结构层面linkedNodes是NodeData上的一个字段linkedNodes: Recordstring, NodeId见 nodes.ts即以id为键、Linked Node id 为值的映射。查询侧也有配套的linkedNodes()helper见 NodeHelpers.ts供query.node(...)使用。Linked Nodes 在真实示例中的用法在 examples/basic/components/user/Card.js 中可以看到 Linked Nodes 的典型组合用法——用两个带id的 Canvas Element 把 Card 组件拆成上区只允许文本、下区只允许按钮的两个可拖放区域export const Card ({ background, padding 20, ...props }) { return ( Container {...props} background{background} padding{padding} Element canvas idtext is{CardTop}>const Hero () { const { css } useNode(node ({ css: node.data.custom.css })); return ( div style{css} h3Im a Hero/h3 Element iddrop is{Container} canvas h3Hi/h3 /Element /div ) } Hero.craft { custom: { css: { background: #eee } } }关键点在于useNode的收集函数node.data.custom.css正是从 Node 数据中读取custom字段。custom在UserComponentConfig中被定义为Recordstring, any见 nodes.ts因此它完全由你自由定义结构。通过Element /覆盖运行时值若想在调用组件时实际设置这些值通过Element /的customprop 即可Frame Element is{Hero} custom{{ css: { background: #ddd } }} / /Frame这样Hero 组件渲染时useNode取到的css将是{ background: #ddd }覆盖了craft.custom中定义的默认值{ background: #eee }。这一机制让编辑器外部也能以声明式方式配置组件数据与运行时的属性面板Settings Panel读写形成互补。补充除了读取你还可以在组件内部通过useNode返回的actions.setCustom(cb)来更新Node 的custom数据见 useInternalNode.ts它同样支持可选的throttleRate节流参数便于拖拽等高频交互场景下控制更新频率。隐藏节点hidden属性Element /的hiddenprop 对应 Node 数据中的hidden字段NodeData.hidden: boolean。当它为true时该 Node 会被隐藏。隐藏的生效位置在渲染层RenderNodeToElement渲染前会通过useInternalNode((node) ({ hidden: node.data.hidden }))收集hidden状态若为true则直接返回null即不渲染任何 DOM见 RenderNode.tsx。这与组件不存在的区别在于Node 及其子节点仍然保留在编辑器状态树中只是不显示。该字段也可通过useNode提供的actions.setHidden(bool)在运行时切换见 useInternalNode.ts适合实现显示/隐藏某个区块这类编辑功能。底层原理Element 如何被解析为 Node要真正理解Element /还需要知道它背后的解析链路。Frame /与Element /内部的 Linked Node 创建最终都会调用query.parseReactElement(jsx).toNodeTree(...)parseNodeFromJSXparseNodeFromJSX.tsx负责把 React 元素或字符串转换为单个 Node它提取element.type作为 Node 的type把element.props展开后作为 Node 的props字符串会被包装为Fragment元素再处理toNodeTree在此基础上递归遍历子元素将整棵 JSX 树转换为NodeTree{ rootNodeId, nodes }结构见 nodes.ts转换完成后Node 会被写入编辑器状态state.nodes[id]并维护parent、nodes、linkedNodes等树形关系见 actions.ts 中的addNodeTreeToParent迭代逻辑。由于解析时需要根据type查找组件所有在 JSX 中使用到的用户组件都必须注册进Craft resolver{{...}}示例中的resolver{{MyComp}}即为此用途否则会触发ERROR_NOT_IN_RESOLVER错误见 constants.ts。实践小结围绕Element /的几个关键结论配置节点作为Frame /子元素时Element is{Comp} canvas custom{{...}} hidden /用于配置被自动创建的 Node创建 Linked Node在 User Component 内部使用Element idxxx ...会创建与宿主组件关联的独立 Nodeid必填否则抛ERROR_TOP_LEVEL_ELEMENT_NO_IDCanvas Nodecanvas决定节点是否同时作为拖放目标droppable对应 Node 数据的isCanvas字段旧Canvas /组件已废弃请使用Element canvas /自定义数据custom提供 Node 级自定义数据存储配合craft.custom默认值与useNode的node.data.custom读取、setCustom更新形成完整闭环隐藏节点hidden让 Node 保留在状态树中但跳过渲染可通过setHidden动态切换。掌握了这些之后无论是搭建从零开始的页面编辑器还是为既有复合组件划分多个可编辑子区域Element /都是你手中最重要的声明式工具。结合本文提到的 Element.tsx、Frame.tsx、actions.ts、interfaces/nodes.ts 等源码继续阅读可以进一步深入 craft.js 的节点模型与状态管理设计。赞分享前端【免费下载链接】craft.js A React Framework for building extensible drag and drop page editors项目地址https://gitcode.com/gh_mirrors/cr/craft.js点击查看免费下载相关推荐深入理解ElixirLS架构前端无关的智能开发服务核心原理深入理解ElixirLS架构前端无关的智能开发服务核心原理 ElixirLS是一个基于语言服务器协议 LSP 的前端无关IDE智能服务器为Elixir编程语n8n-nodes-mcp 开发实战如何创建自定义MCP工具节点n8n nodes mcp 开发实战如何创建自定义MCP工具节点 Model Context Protocol MCP 是连接AI模型与外部工具的标准化协议从零构建你的专属节点G6 自定义节点完整实战指南从零构建你的专属节点G6 自定义节点完整实战指南 G6 内置了 circle、rect、diamond、donut、ellipse、hexagon、html、数据可视化前端图表库上一篇XHS-Downloader终极指南三步搞定小红书无水印批量下载的完整解决方案下一篇小红书无水印下载神器5分钟快速上手XHS-Downloader终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表