LogicFlow 渲染与数据读写指南:render / getGraphData / clearData 与 adapter 适配机制详解
【免费下载链接】LogicFlowA flow chart editing framework focus on business customization. 专注于业务自定义的流程图编辑框架,支持实现脑图、ER图、UML、工作流等各种图编辑场景。项目地址: https://gitcode.com/GitHub_Trending/lo/LogicFlow
本篇技术指南围绕 LogicFlow 实例的渲染入口与数据读写 API 展开,涵盖render、renderRawData、getGraphData、getGraphRawData、clearData以及adapterIn/adapterOut双适配器钩子。通过阅读本文,你将掌握如何将外部业务数据(如 BPMN JSON、自研 DSL)接入 LogicFlow 画布、如何取出被适配后的数据,以及如何基于适配器机制构建与业务数据结构解耦的通用图编辑能力。
一、总体概览:渲染与数据的两个通道
LogicFlow 实例(LogicFlow类)在数据层面刻意设计了两条通道:
- 原生通道(Native):直接使用 LogicFlow 内部定义的
GraphData结构({ nodes, edges }),不经过任何转换。对应renderRawData与getGraphRawData。 - 适配通道(Adapted):先经过
adapterIn将业务数据转换为内部结构再渲染,或在导出时经过adapterOut将内部结构转换为业务结构。对应render与getGraphData。
这一设计使得 LogicFlow 既能作为"开箱即用"的流程图框架,也能无缝嵌入已有业务系统——业务方只需要维护一套数据映射函数,而不必改造画布内部模型。相关的核心类型定义位于 packages/core/src/LogicFlow.tsx,完整类型字典可参考 sites/docs/docs/api/type/MainTypes.en.md。
二、渲染入口:render 与 renderRawData
2.1 render:经过 adapterIn 的渲染入口
签名
render(graphData: unknown): void参数
| 名称 | 类型 | 必填 | 说明 |
|---|---|---|---|
graphData | unknown | 是 | 图数据载荷;具体结构取决于adapterIn的约定。 |
示例
lf.render({ nodes: [{ id: 'node_1', type: 'rect', x: 120, y: 100 }], edges: [], });从源码实现看,render的调用链非常清晰(packages/core/src/LogicFlow.tsx):
render(graphData: GraphConfigData) { let graphRawData = cloneDeep(graphData) if (this.adapterIn) { graphRawData = this.adapterIn(graphRawData) } this.renderRawData(graphRawData) }几点值得注意的实现细节:
- 先
cloneDeep再转换:传入的graphData会被深拷贝,避免适配过程中意外修改调用方持有的原始对象。 adapterIn可选:未配置adapterIn时,render等价于直接调用renderRawData,此时graphData必须是原生GraphConfigData结构。- 并非增量更新:
render会重建整个画布模型(见下文renderRawData),用于初始化或整体替换图数据,而不是在现有画布上追加节点。
2.2 renderRawData:免适配的原生渲染
签名
renderRawData(graphData: GraphData): void参数
| 名称 | 类型 | 必填 | 说明 |
|---|---|---|---|
graphData | GraphData | 是 | LogicFlow 原生图数据({ nodes, edges })。 |
示例
lf.renderRawData({ nodes: [{ id: 'node_1', type: 'rect', x: 120, y: 100 }], edges: [], });renderRawData是真正的底层渲染实现(packages/core/src/LogicFlow.tsx):
renderRawData(graphRawData: GraphConfigData) { this.graphModel.graphDataToModel(formatData(graphRawData)) if (this.options.history !== false) { this.history.watch(this.graphModel) } render( <Graph getView={this.getView} tool={this.tool} options={this.options} dnd={this.dnd} snaplineModel={this.snaplineModel} graphModel={this.graphModel} />, this.container, ) this.emit(EventType.GRAPH_RENDERED, { data: this.graphModel.modelToGraphData(), graphModel: this.graphModel, }) }调用链说明:
formatData(graphRawData):对输入数据做兼容性归一化(兼容 Vue 等响应式包装的数据);graphDataToModel:将配置数据转换为内部 Model(见 packages/core/src/model/GraphModel.ts)。转换过程中每个节点会经过getModelAfterSnapToGrid吸附到网格,边则通过getModel(edge.type ?? currEdgeType)找到注册的边模型,若类型未注册会抛出找不到${edge.type}对应的边。错误——这意味着renderRawData要求所有用到的节点/边类型都已被register注册;- 历史记录绑定:当实例选项
history不为false时,为graphModel启动历史监听,从而支持undo/redo; - 渲染 Graph 根组件:将
Graph视图挂载到实例容器,此时节点、边、文本真正出现在画布上; - 派发
GRAPH_RENDERED事件:渲染完成后触发,回调中携带data(当前画布数据的快照)与graphModel,可在渲染完成后立即拿到最新模型做后续处理。
因此,建议通用插件内部一律使用renderRawData而非render,以避免适配器干扰插件对数据结构的假设。
三、数据导出:getGraphData 与 getGraphRawData
3.1 getGraphData:可携带额外参数的导出
签名
getGraphData(...params: any[]): GraphConfigData | unknown参数
| 名称 | 类型 | 必填 | 说明 |
|---|---|---|---|
...params | any[] | 否 | 透传给adapterOut的额外参数。 |
返回值
- 未配置
adapterOut:返回GraphConfigData(原生结构); - 配置了
adapterOut:返回你的业务结构(unknown)。
示例
const data = lf.getGraphData(['property1', 'property2']);源码实现(packages/core/src/LogicFlow.tsx)表明,getGraphData内部先取原生数据,再交给adapterOut:
getGraphData(...params: any): GraphData | unknown { const data = this.getGraphRawData() if (this.adapterOut) { return this.adapterOut(data, ...params) } return data }其中...params被原样透传,这使得业务侧可以在调用导出时动态传入额外信息(例如:是否包含坐标、需要保留的业务字段名等),由adapterOut决定如何使用。
3.2 getGraphRawData:免适配的原生导出
签名
getGraphRawData(): GraphData返回值
GraphData:{ nodes: NodeData[], edges: EdgeData[] }。
示例
const rawData = lf.getGraphRawData(); console.log(rawData.nodes, rawData.edges);实现为一行(packages/core/src/LogicFlow.tsx):
getGraphRawData(): GraphData { return this.graphModel.modelToGraphData() }modelToGraphData(packages/core/src/model/GraphModel.ts)遍历内部edges与nodes,逐个调用getData()收集数据,并过滤掉virtual标记的元素——这保证了诸如拖拽预览、临时连线等虚拟元素不会泄漏进导出的数据。
3.3 官方注释给出的选型建议
源码中对getGraphData的注释(packages/core/src/LogicFlow.tsx)明确提示:
getGraphData 返回的数据受到 adapter 影响,所以其数据格式不一定是 logicflow 内部图数据格式。如果实现通用插件,请使用 getGraphRawData。
这是插件开发者的重要约定:依赖内部结构的代码(如快照、布局、校验插件)必须走getGraphRawData/renderRawData原生通道;只有面向业务输出/输入时才使用适配通道。
四、清空画布:clearData
签名
clearData(): void示例
lf.clearData();clearData的实现(packages/core/src/LogicFlow.tsx):
clearData() { this.graphModel.clearData() // 强制刷新数据, 让 preact 清除对已删除节点的引用 this.render({}) }模型层的clearData(packages/core/src/model/GraphModel.ts)会清空nodes、edges,并同步清理edgeModelMap、nodeModelMap、elementsModelMap三张索引表,防止内存中残留对已删除元素的引用。随后render({})触发一次空数据渲染,让视图层彻底释放对被删除节点的引用。
需要区分两个概念:
clearData():清空画布上的所有节点与边(元素级清空),画布实例本身保留;lf.destroy():销毁整个实例与容器(见 packages/core/src/LogicFlow.tsx),适用于页面卸载场景。
五、适配器钩子:adapterIn 与 adapterOut
适配器是 LogicFlow 与业务数据格式解耦的核心机制。两者均为实例上的可选属性,直接赋值即可生效。
5.1 adapterIn:入站适配
签名
adapterIn?: (data: unknown) => GraphData返回值
GraphData:转换为 LogicFlow 原生图数据。
示例
lf.adapterIn = (bizData) => { return { nodes: [], edges: [], }; };典型用法:业务后端返回的数据结构(例如字段名为processNodes/processEdges,坐标字段为cx/cy)与 LogicFlow 的NodeConfig/EdgeConfig不一致时,在adapterIn中做字段映射、坐标换算(如中心点坐标系、左上角坐标系互转)以及数据清洗,保证render收到的永远是干净的内部结构。
5.2 adapterOut:出站适配
签名
adapterOut?: (data: GraphConfigData, ...params: any[]) => unknown参数
| 名称 | 类型 | 必填 | 说明 |
|---|---|---|---|
data | GraphConfigData | 是 | 当前画布的原生图数据。 |
...params | any[] | 否 | 与getGraphData传入的额外参数一致。 |
示例
lf.adapterOut = (data, reserveFields = []) => { return { processNodes: data.nodes, processEdges: data.edges, reserveFields, }; };典型用法:将内部{ nodes, edges }转换为业务侧的协议结构,同时利用...params支持"本次导出是否包含布局信息""需要保留的字段白名单"等调用期动态选项。getGraphData在adapterOut存在时返回unknown(即业务结构),不存在时退化为返回GraphConfigData。
5.3 真实案例:BPMN 适配器
adapterIn/adapterOut并非纸面概念,LogicFlow 官方扩展中的 BPMN 适配器就是完整实践(packages/extension/src/bpmn-adapter/index.ts)。
其目录注释(packages/extension/src/bpmn-adapter/index.ts)点明了双向职责:
adapterOut:将 LogicFlow 图数据转换为 BPMN JSON(随后由json2xml转为 XML);adapterIn:将 BPMN JSON 转换为 LogicFlow 图数据(如果是 XML,则先经xml2json转为 JSON)。
在BpmnAdapter中,适配器被注入到实例上(packages/extension/src/bpmn-adapter/index.ts):
lf.adapterIn = (data) => this.adapterIn(data) lf.adapterOut = (data, retainedFields?: string[]) => this.adapterOut(data, retainedFields)坐标换算的细节也印证了适配器存在的必要性(packages/extension/src/bpmn-adapter/index.ts):
bpmn坐标是基于左上角,LogicFlow基于中心点,此处处理一下。
此外还提供BpmnXmlAdapter变体(packages/extension/src/bpmn-adapter/index.ts),将adapterIn/adapterOut替换为面向 XML 的adapterXmlIn/adapterXmlOut——内部先调xml2json再复用基础适配逻辑(见 packages/extension/src/bpmn-adapter/index.ts)。这展示了适配器可以叠加管道:XML → JSON → GraphData,逐层解耦。
六、数据类型速查
渲染与数据 API 依赖的核心类型定义如下(源码位于 packages/core/src/LogicFlow.tsx,详细字段说明见 sites/docs/docs/api/type/MainTypes.en.md)。
GraphConfigData 与 GraphData
export interface GraphConfigData { nodes?: NodeConfig[] edges?: EdgeConfig[] } export interface GraphData { nodes: NodeData[] edges: EdgeData[] }两者的区别:GraphConfigData是"输入配置"(nodes/edges均可省略,节点id可缺省由框架自动生成),GraphData是"输出快照"(nodes/edges必填,所有id已解析)。getGraphRawData返回GraphData,renderRawData接受GraphConfigData。
NodeConfig / NodeData 关键字段
| 字段 | 类型 | 说明 |
|---|---|---|
id | string(可选) | 节点标识,省略时自动生成。 |
type | string(必填) | 已注册的节点类型名。 |
x/y | number | 画布坐标(中心点基准)。 |
text | TextConfig \| string(可选) | 节点文本。 |
zIndex | number(可选) | 层级。 |
properties | PropertiesType(可选) | 业务自定义属性容器。 |
virtual | boolean(可选) | 标记为虚拟节点(不进入导出数据)。 |
rotate | number(可选) | 旋转角度。 |
rotatable/resizable | boolean(可选) | 交互控制开关。 |
EdgeConfig / EdgeData 关键字段
| 字段 | 类型 | 说明 |
|---|---|---|
type | string(可选) | 边类型,内部默认为polyline。 |
sourceNodeId/targetNodeId | string(必填) | 起止节点 id。 |
sourceAnchorId/targetAnchorId | string(可选) | 起止锚点 id。 |
startPoint/endPoint | Point(可选) | 手动指定的起止坐标。 |
pointsList | Point[](可选) | 折线拐点。 |
text | string \| TextConfig(可选) | 边标签。 |
七、实战组合模式
将以上 API 组合起来,可以形成一套标准的数据加载—编辑—导出闭环:
// 1. 加载业务数据(业务结构,如后端 DSL) lf.adapterIn = (biz) => ({ nodes: biz.processNodes.map((n) => ({ id: n.id, type: n.type, x: n.cx, y: n.cy, properties: n.props, })), edges: biz.processEdges, }); lf.render(bizData); // 2. 编辑过程中,需要内部结构做校验/布局时走原生通道 const raw = lf.getGraphRawData(); // GraphData,永远稳定 // 3. 保存时输出业务结构,支持调用期传入额外选项 lf.adapterOut = (data, { keepLayout }) => ({ processNodes: data.nodes.map((n) => (keepLayout ? n : { ...n, x: 0, y: 0 })), processEdges: data.edges, }); const saved = lf.getGraphData({ keepLayout: false }); // 4. 重新编辑或重置 lf.clearData();八、最佳实践小结
- 插件开发者走原生通道:内部依赖结构的通用插件一律使用
renderRawData/getGraphRawData,避免被业务适配器干扰。 - 业务接入走适配通道:
render+adapterIn、getGraphData+adapterOut的组合将数据结构差异隔离在薄薄一层映射函数中。 adapterOut善用...params:把"导出选项"设计为getGraphData的调用期参数,而不是全局状态,保证适配器可复用、可测试。- 注意
render的整图替换语义:需要增量增删节点时,应使用addNode/addEdge等模型 API,而不是反复调用render。 - 渲染后如需立即操作模型:监听
GRAPH_RENDERED事件,回调中即可拿到graphModel与数据快照。
以上所有实现细节均可直接在仓库源码中验证:实例方法实现见 packages/core/src/LogicFlow.tsx,模型层数据转换见 packages/core/src/model/GraphModel.ts,类型定义见 sites/docs/docs/api/type/MainTypes.en.md。
【免费下载链接】LogicFlowA flow chart editing framework focus on business customization. 专注于业务自定义的流程图编辑框架,支持实现脑图、ER图、UML、工作流等各种图编辑场景。项目地址: https://gitcode.com/GitHub_Trending/lo/LogicFlow
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考