React Flow 实战(二)—— 拖拽添加节点
<p><span style="font-size: 14px">上一篇 《React Flow 实战》介绍了自定义节点等基本操作,接下来就该撸一个真正的流程图了</span></p><p><span style="font-size: 14px"><img src="https://img2020.cnblogs.com/blog/1059788/202111/1059788-20211116192037049-1561314809.gif" alt="" loading="lazy"></span></p>
<p id="1637061637435"><span style="font-size: 14px"> </span></p>
<p> </p>
<p><span style="font-size: 16px"><strong>一、ReactFlowProvider</strong></span></p>
<p><span style="font-size: 14px">React Flow 提供了两个 Hooks 来处理画布数据:</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px"><span style="color: rgba(0, 0, 0, 1)">import {
<span style="color: rgba(0, 0, 255, 1)">useStoreState</span>,
<span style="color: rgba(0, 0, 255, 1)">useStoreActions</span>
} </span><span style="color: rgba(0, 0, 0, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">react-flow-renderer</span><span style="color: rgba(128, 0, 0, 1)">'</span>;</span></pre>
</div>
<p><span style="font-size: 14px">通常情况下可以直接使用它们来获取 nodes、edges</span></p>
<p><span style="font-size: 14px">但<span style="color: rgba(128, 0, 0, 1)"><strong>如果页面上同时存在多个 </strong></span><span style="color: rgba(0, 128, 128, 1)"><strong>ReactFlow</strong></span><span style="color: rgba(128, 0, 0, 1)"><strong>,或者需要在 <span style="color: rgba(0, 128, 128, 1)">ReactFlow</span> 外部操作画布数据</strong></span>,就需要使用 ReactFlowProvider 将整个画布包起来</span></p>
<p><span style="font-size: 14px">于是整个流程图的入口文件 index.jsx 是这样的:</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px"><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> index.jsx</span>
<span style="color: rgba(0, 0, 0, 1)">
import React, { useState } </span><span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">react</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
import { ReactFlowProvider } </span><span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">react-flow-renderer</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
import Sider </span><span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">./Sider</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
import Graph </span><span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">./Graph</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
import Toolbar </span><span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">./Toolbar</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
import flowStyles </span><span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">./index.module.less</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
export </span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)"> function FlowPage() {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 画布实例</span>
<span style="color: rgba(0, 0, 255, 1)">const</span> = useState(<span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
</span><div className={flowStyles.container}>
<ReactFlowProvider><span style="color: rgba(0, 0, 0, 1)">
{</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> 顶部工具栏 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">}
</span><Toolbar instance={reactFlowInstance} />
<div className={flowStyles.main}><span style="color: rgba(0, 0, 0, 1)">
{</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> 侧边栏,展示可拖拽的节点 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">}
</span><Sider /><span style="color: rgba(0, 0, 0, 1)">
{</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> 画布,处理核心逻辑 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">}
</span><<span style="color: rgba(0, 0, 0, 1)">Graph
instance</span>=<span style="color: rgba(0, 0, 0, 1)">{reactFlowInstance}
setInstance</span>=<span style="color: rgba(0, 0, 0, 1)">{setReactFlowInstance}
</span>/>
</div>
</ReactFlowProvider>
</div><span style="color: rgba(0, 0, 0, 1)">
);
}</span></span></pre>
</div>
<p><span style="font-size: 14px">这里创建了 reactFlowInstance 这个状态,用来保存 ReactFlow 创建后的实例</span></p>
<p><span style="font-size: 14px">这个实例会在 <span style="color: rgba(0, 128, 128, 1)"><strong>Graph</strong></span> 中设置,但会在 Graph 和 Toolbar 中使用,所以将该状态提升到 index.js 中管理</span></p>
<p><span style="font-size: 14px">但这种将 state 和 setState 都传给子组件的方式并不好,最好是使用 <span style="color: rgba(153, 51, 0, 1)"><strong>useReducer</strong></span> 加以改造,或者引入状态管理节制</span></p>
<hr>
<p><span style="font-size: 14px"> </span></p>
<p><span style="font-size: 14px">整体的目录结构如下</span></p>
<p><span style="font-size: 14px"><img src="https://img2020.cnblogs.com/blog/1059788/202111/1059788-20211116185112059-796700655.png" alt="" width="259" height="291" loading="lazy"></span></p>
<p><span style="font-size: 14px"> </span></p>
<p><span style="font-size: 14px"> </span></p>
<p><span style="font-size: 16px"><strong>二、拖拽添加节点</strong></span></p>
<p><span style="font-size: 14px">简单的拖拽添加节点,可以通过原生 API draggable 实现</span></p>
<p><span style="font-size: 14px">在 <span style="color: rgba(0, 128, 128, 1)"><strong>Sider</strong></span> 中触发节点的 <span style="color: rgba(128, 0, 0, 1)"><strong>onDragStart</strong></span> 事件,然后在 <span style="color: rgba(0, 128, 128, 1)"><strong>Graph</strong></span> 中通过 ReactFlow <span style="color: rgba(128, 0, 0, 1)"><strong>onDrop</strong></span> 来接收</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px"><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Sider.jsx</span>
<span style="color: rgba(0, 0, 0, 1)">
import React </span><span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">react</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
import classnames </span><span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">classnames</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
import { useStoreState } </span><span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">react-flow-renderer</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
import flowStyles </span><span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">../index.module.less</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 可用节点</span>
<span style="color: rgba(0, 0, 255, 1)">const</span> allowedNodes =<span style="color: rgba(0, 0, 0, 1)"> [
{
name: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Input Node</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
className: flowStyles.inputNode,
type: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">input</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
},
{
name: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Relation Node</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
className: flowStyles.relationNode,
type: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">relation</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 这是自定义节点类型</span>
<span style="color: rgba(0, 0, 0, 1)">},
{
name: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Output Node</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
className: flowStyles.outputNode,
type: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">output</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
},
];
export </span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)"> function FlowSider() {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取画布上的节点</span>
<span style="color: rgba(0, 0, 255, 1)">const</span> nodes = useStoreState((store) =><span style="color: rgba(0, 0, 0, 1)"> store.nodes);
</span><span style="color: rgba(0, 0, 255, 1)">const</span> onDragStart = (evt, nodeType) =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 记录被拖拽的节点类型</span>
evt.dataTransfer.setData(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">application/reactflow</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">, nodeType);
evt.dataTransfer.effectAllowed </span>= <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">move</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
};
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
</span><div className={flowStyles.sider}>
<div className={flowStyles.nodes}><span style="color: rgba(0, 0, 0, 1)">
{allowedNodes.map((x, i) </span>=><span style="color: rgba(0, 0, 0, 1)"> (
</span><<span style="color: rgba(0, 0, 0, 1)">div
key</span>={`${x.type}-<span style="color: rgba(0, 0, 0, 1)">${i}`}
className</span>=<span style="color: rgba(0, 0, 0, 1)">{classnames()}
onDragStart</span>={e =><span style="color: rgba(0, 0, 0, 1)"> onDragStart(e, x.type)}
draggable
</span>><span style="color: rgba(0, 0, 0, 1)">
{x.name}
</span></div><span style="color: rgba(0, 0, 0, 1)">
))}
</span></div>
<div className={flowStyles.print}>
<div className={flowStyles.printLine}><span style="color: rgba(0, 0, 0, 1)">
节点数量:{ nodes</span>?.length || <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">-</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)"> }
</span></div>
<ul className={flowStyles.printList}><span style="color: rgba(0, 0, 0, 1)">
{
nodes.map((x) </span>=><span style="color: rgba(0, 0, 0, 1)"> (
</span><li key={x.id} className={flowStyles.printItem}>
<span className={flowStyles.printItemTitle}>{x.data.label}</span>
<span className={flowStyles.printItemTips}>({x.type})</span>
</li><span style="color: rgba(0, 0, 0, 1)">
))
}
</span></ul>
</div>
</div><span style="color: rgba(0, 0, 0, 1)">
);
}</span></span></pre>
</div>
<p><span style="font-size: 14px">上面还通过 <span style="color: rgba(128, 0, 0, 1)"><strong>useStoreState</strong></span> 拿到了画布上的节点信息 nodes,该 nodes 基于 Redux 管理,无需手动更新</span></p>
<hr>
<p><span style="font-size: 14px"> </span></p>
<p><span style="font-size: 14px">在 <strong><span style="color: rgba(0, 128, 128, 1)">Graph</span></strong> 中,首先需要通过 <span style="color: rgba(153, 51, 0, 1)"><strong>onLoad </strong></span>回调得到 ReactFlow 实例</span></p>
<p><span style="font-size: 14px">接着处理 <span style="color: rgba(128, 0, 0, 1)"><strong>onDragOver</strong></span> 事件,更新 dropEffect,和 effectAllowed 保持一致</span></p>
<p><span style="font-size: 14px">然后在 <span style="color: rgba(128, 0, 0, 1)"><strong>onDrop</strong></span> 事件处理函数中,通过 <span style="color: rgba(153, 51, 0, 1)"><strong>getBoundingClientRect</strong></span> 获取画布容器的坐标信息</span></p>
<p><span style="font-size: 14px">但坐标信息需要通过 ReactFlow 实例提供的 project 方法处理为 ReactFlow 坐标系</span></p>
<p><span style="font-size: 14px">最后组装节点信息,更新 elements 即可</span> </p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Graph/index.jsx</span>
<span style="color: rgba(0, 0, 0, 1)">
import React, { useState, useRef } </span><span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">react</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
import ReactFlow, { Controls } </span><span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">react-flow-renderer</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
import RelationNode </span><span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">../Node/relationNode</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
import flowStyles </span><span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">../index.module.less</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
function getHash(len) {
let length </span>= Number(len) || <span style="color: rgba(128, 0, 128, 1)">8</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">const</span> arr =
<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789</span><span style="color: rgba(128, 0, 0, 1)">'</span>.split(<span style="color: rgba(128, 0, 0, 1)">''</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">const</span> al =<span style="color: rgba(0, 0, 0, 1)"> arr.length;
let chars </span>= <span style="color: rgba(128, 0, 0, 1)">''</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">while</span> (length--<span style="color: rgba(0, 0, 0, 1)">) {
chars </span>+= arr;
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> chars;
}
export </span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)"> function FlowGraph(props) {
</span><span style="color: rgba(0, 0, 255, 1)">const</span> { setInstance, instance } =<span style="color: rgba(0, 0, 0, 1)"> props;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 画布的 DOM 容器,用于计算节点坐标</span>
<span style="color: rgba(0, 0, 255, 1)">const</span> graphWrapper = useRef(<span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 节点、连线 都通过 elements 来维护</span>
<span style="color: rgba(0, 0, 255, 1)">const</span> = useState(props.elements ||<span style="color: rgba(0, 0, 0, 1)"> []);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 自定义节点</span>
<span style="color: rgba(0, 0, 255, 1)">const</span> nodeTypes =<span style="color: rgba(0, 0, 0, 1)"> {
relation: RelationNode,
};
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 画布加载完毕,保存当前画布实例</span>
<span style="color: rgba(0, 0, 255, 1)">const</span> onLoad = (instance) =><span style="color: rgba(0, 0, 0, 1)"> setInstance(instance);
</span><span style="color: rgba(0, 0, 255, 1)">const</span> onDrop = (<span style="color: rgba(0, 0, 255, 1)">event</span>) =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">event</span><span style="color: rgba(0, 0, 0, 1)">.preventDefault();
</span><span style="color: rgba(0, 0, 255, 1)">const</span> reactFlowBounds =<span style="color: rgba(0, 0, 0, 1)"> graphWrapper.current.getBoundingClientRect();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取节点类型</span>
<span style="color: rgba(0, 0, 255, 1)">const</span> type = <span style="color: rgba(0, 0, 255, 1)">event</span>.dataTransfer.getData(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">application/reactflow</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用 project 将像素坐标转换为内部 ReactFlow 坐标系</span>
<span style="color: rgba(0, 0, 255, 1)">const</span> position =<span style="color: rgba(0, 0, 0, 1)"> instance.project({
x: </span><span style="color: rgba(0, 0, 255, 1)">event</span>.clientX -<span style="color: rgba(0, 0, 0, 1)"> reactFlowBounds.left,
y: </span><span style="color: rgba(0, 0, 255, 1)">event</span>.clientY -<span style="color: rgba(0, 0, 0, 1)"> reactFlowBounds.top,
});
</span><span style="color: rgba(0, 0, 255, 1)">const</span> newNode =<span style="color: rgba(0, 0, 0, 1)"> {
id: getHash(),
type,
position,
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 传入节点 data</span>
<span style="color: rgba(0, 0, 0, 1)"> data: { label: `${type} node` },
};
setElements((els) </span>=><span style="color: rgba(0, 0, 0, 1)"> els.concat(newNode));
};</span><span style="color: rgba(0, 0, 255, 1)">const</span> onDragOver = (<span style="color: rgba(0, 0, 255, 1)">event</span>) =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">event</span><span style="color: rgba(0, 0, 0, 1)">.preventDefault();
</span><span style="color: rgba(0, 0, 255, 1)">event</span>.dataTransfer.dropEffect = <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">move</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
};
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
</span><div className={flowStyles.graph} <span style="color: rgba(0, 0, 255, 1)">ref</span>={graphWrapper}>
<<span style="color: rgba(0, 0, 0, 1)">ReactFlow
elements</span>=<span style="color: rgba(0, 0, 0, 1)">{elements}
nodeTypes</span>=<span style="color: rgba(0, 0, 0, 1)">{nodeTypes}</span><span style="color: rgba(0, 0, 0, 1)">
onLoad</span>=<span style="color: rgba(0, 0, 0, 1)">{onLoad}
onDrop</span>=<span style="color: rgba(0, 0, 0, 1)">{onDrop}
onDragOver</span>=<span style="color: rgba(0, 0, 0, 1)">{onDragOver}
</span>>
<Controls />
</ReactFlow>
</div><span style="color: rgba(0, 0, 0, 1)">
);
}</span></pre>
</div>
<p><span style="font-size: 14px">完成以上逻辑,就能够从侧边栏拖拽节点添加到画布上了</span></p>
<p><span style="color: rgba(136, 136, 136, 1); font-size: 14px">// 可以先删除以上有关自定义节点 RelationNode 的代码,试试拖拽功能</span></p>
<p><span style="font-size: 14px">但目前的节点只是展示出来了,暂时不能连线,或者更新节点数据,后面逐步完善</span></p>
<p><span style="font-size: 14px"> </span></p>
<p><span style="font-size: 14px"> </span></p>
<p><span style="font-size: 16px"><strong>三、连线</strong></span></p>
<p><span style="font-size: 14px">在画布上连线的时候,会触发 ReactFlow <span style="color: rgba(128, 0, 0, 1)"><strong>onConnect</strong></span> 事件,并提供连线信息</span></p>
<p><span style="font-size: 14px"><img src="https://img2020.cnblogs.com/blog/1059788/202111/1059788-20211116112011554-1801360779.png" alt="" loading="lazy"></span></p>
<p><span style="font-size: 14px">然后通过 addEdge 来添加连线,这个方法接收两个参数 <strong><span style="color: rgba(128, 0, 0, 1)">edgeParams</span></strong> 和 <strong><span style="color: rgba(128, 0, 0, 1)">elements</span></strong>,最后返回全新的 <span style="color: rgba(128, 0, 0, 1)"><strong>elements</strong></span></span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px"><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Graph/index.jsx</span>
<span style="color: rgba(0, 0, 0, 1)">
import ReactFlow, { addEdge } </span><span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">react-flow-renderer</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ...</span>
<span style="color: rgba(0, 0, 0, 1)">
export </span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)"> function FlowGraph(props) {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ...
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 连线</span>
<span style="color: rgba(0, 0, 255, 1)">const</span> onConnect = <span style="color: rgba(0, 0, 255, 1)">params</span> => setElements(els => addEdge(<span style="color: rgba(0, 0, 255, 1)">params</span><span style="color: rgba(0, 0, 0, 1)">, els));
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (</span>
<<span style="color: rgba(0, 0, 0, 1)">ReactFlow
elements</span>=<span style="color: rgba(0, 0, 0, 1)">{elements}
onConnect</span>=<span style="color: rgba(0, 0, 0, 1)">{onConnect}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> other...</span>
/><span style="color: rgba(0, 0, 0, 1)">
);
}</span></span></pre>
</div>
<p><span style="font-size: 14px">如果需要设置连线类型,或者设置其他连线的信息,都可以通过 addEdge 的第一个参数来设置</span></p>
<p><span style="font-size: 14px">从节点出口拉出的线,在连接到节点入口前,默认展示的是 bezier 类型的线</span></p>
<p><span style="font-size: 14px">如果需要自定义<span style="color: rgba(128, 0, 0, 1)"><strong>连接中的线</strong></span>的样式,可以使用 <span style="color: rgba(0, 128, 128, 1)"><strong>connectionLineComponent</strong></span>,具体可以参考官方示例</span></p>
<p><span style="font-size: 14px">另外,还可以通过 <span style="color: rgba(0, 128, 128, 1)"><strong>onEdgeUpdate</strong></span> 来更改连线的起点或终点,参考官方示例</span></p>
<p><span style="font-size: 14px"> </span></p>
<p><span style="font-size: 14px"> </span></p>
<p><span style="font-size: 16px"><strong>四、获取画布数据</strong></span></p>
<p><span style="font-size: 14px">在最开始的 index.jsx 中维护了一份 ReactFlow 的画布实例 reactFlowInstance,并传给了 Graph 和 Toolbar</span></p>
<p><span style="font-size: 14px">通过 reactFlowInstance 就可以很方便的获取画布数据</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px"><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Toolbar.jsx</span>
<span style="color: rgba(0, 0, 0, 1)">
import React, { useCallback } </span><span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">react</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
import classnames </span><span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">classnames</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
import flowStyles </span><span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">../index.module.less</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
export </span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)"> function Toolbar({ instance }) {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 保存</span>
<span style="color: rgba(0, 0, 255, 1)">const</span> handleSave = useCallback(() =><span style="color: rgba(0, 0, 0, 1)"> {
console.log(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">toObject</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">, instance.toObject());
}, );
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
</span><div className={flowStyles.toolbar}>
<<span style="color: rgba(0, 0, 0, 1)">button
className</span>=<span style="color: rgba(0, 0, 0, 1)">{classnames()}
onClick</span>=<span style="color: rgba(0, 0, 0, 1)">{handleSave}
</span>><span style="color: rgba(0, 0, 0, 1)">
保存
</span></button>
</div><span style="color: rgba(0, 0, 0, 1)">
);
}</span></span></pre>
</div>
<p><span style="font-size: 14px"><img src="https://img2020.cnblogs.com/blog/1059788/202111/1059788-20211116190915471-705582953.png" alt="" loading="lazy"></span></p>
<p><span style="font-size: 14px">上面使用的是<span style="color: rgba(128, 0, 0, 1)"><strong> Instance.toObject</strong></span>,拿到的是画布的全量数据,如果只需要 elements 可以使用<span style="color: rgba(128, 0, 0, 1)"><strong> Instance.getElements</strong></span></span></p>
<p><span style="font-size: 14px">完整的实例方法可以参考官方文档</span></p>
<p><span style="font-size: 14px"> </span></p>
<p><span style="font-size: 14px">除了通过实例获取画布数据,还可以使用 <span style="color: rgba(128, 0, 0, 1)"><strong>useStoreState</strong></span> </span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px">import ReactFlow, { useStoreState } <span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">react-flow-renderer</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">const</span> NodesDebugger = () =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">const</span> nodes = useStoreState((state) =><span style="color: rgba(0, 0, 0, 1)"> state.nodes);
</span><span style="color: rgba(0, 0, 255, 1)">const</span> edges = useStoreState((state) =><span style="color: rgba(0, 0, 0, 1)"> state.edges);
console.log(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">nodes</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">, nodes);
console.log(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">edges</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">, edges);
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
};
</span><span style="color: rgba(0, 0, 255, 1)">const</span> Flow = () =><span style="color: rgba(0, 0, 0, 1)"> (
</span><ReactFlow elements={elements}>
<NodesDebugger />
</ReactFlow><span style="color: rgba(0, 0, 0, 1)">
);</span></span></pre>
</div>
<p><span style="font-size: 14px">但这样获取的 nodes 会携带一些画布信息</span></p>
<p><span style="font-size: 14px"><img src="https://img2020.cnblogs.com/blog/1059788/202111/1059788-20211116191817602-942347857.png" alt="" loading="lazy"></span></p>
<p><span style="font-size: 14px">具体使用哪种方式,可以根据实际的业务场景来取舍 </span></p>
<hr>
<p><span style="font-size: 14px"> </span></p>
<p><span style="font-size: 14px"> </span></p>
<p><span style="font-size: 14px">实际项目中的流程图,通常都会在节点甚至连线上配置各种数据</span></p>
<p><span style="font-size: 14px">我们可以通过 elements 中各个元素的 data 来维护,但这真的合理吗?</span></p>
<p><span style="font-size: 14px">elements 保存了节点和连线的位置、样式信息,用于 ReactFlow 绘制流程图,和业务数据并无关联</span></p>
<p><span style="font-size: 14px">所以我建议<span style="color: rgba(128, 0, 0, 1)"><strong>以 map 的形式单独维护业务数据</strong></span>,可以通过节点或连线的 id 快速查找</span></p>
<p><span style="font-size: 14px">具体的实现方案有很多,下一篇文章将介绍基于 React Context 的流程图数据管理方案</span></p>
<p><span style="font-size: 14px"><span style="color: rgba(136, 136, 136, 1)">// 文章还在施工中,有兴趣可以先看下项目</span> flow-demo-app</span></p>
<p> </p><br><br>
来源:https://www.cnblogs.com/wisewrong/p/15433647.html
頁:
[1]