山之鹰 發表於 2021-11-16 19:22:00

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">&nbsp;</span></p>
<p>&nbsp;</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>,或者需要在&nbsp;<span style="color: rgba(0, 128, 128, 1)">ReactFlow</span> 外部操作画布数据</strong></span>,就需要使用&nbsp;ReactFlowProvider&nbsp;将整个画布包起来</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>&lt;div className={flowStyles.container}&gt;
      &lt;ReactFlowProvider&gt;<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>&lt;Toolbar instance={reactFlowInstance} /&gt;
      &lt;div className={flowStyles.main}&gt;<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>&lt;Sider /&gt;<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>&lt;<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>/&gt;
      &lt;/div&gt;
      &lt;/ReactFlowProvider&gt;
    &lt;/div&gt;<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">这个实例会在&nbsp;<span style="color: rgba(0, 128, 128, 1)"><strong>Graph</strong></span> 中设置,但会在&nbsp;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">&nbsp;</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">&nbsp;</span></p>
<p><span style="font-size: 14px">&nbsp;&nbsp;</span></p>
<p><span style="font-size: 16px"><strong>二、拖拽添加节点</strong></span></p>
<p><span style="font-size: 14px">简单的拖拽添加节点,可以通过原生 API&nbsp;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) =&gt;<span style="color: rgba(0, 0, 0, 1)"> store.nodes);
</span><span style="color: rgba(0, 0, 255, 1)">const</span> onDragStart = (evt, nodeType) =&gt;<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>&lt;div className={flowStyles.sider}&gt;
      &lt;div className={flowStyles.nodes}&gt;<span style="color: rgba(0, 0, 0, 1)">
      {allowedNodes.map((x, i) </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> (
          </span>&lt;<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 =&gt;<span style="color: rgba(0, 0, 0, 1)"> onDragStart(e, x.type)}
            draggable
          </span>&gt;<span style="color: rgba(0, 0, 0, 1)">
            {x.name}
          </span>&lt;/div&gt;<span style="color: rgba(0, 0, 0, 1)">
      ))}
      </span>&lt;/div&gt;
      &lt;div className={flowStyles.print}&gt;
      &lt;div className={flowStyles.printLine}&gt;<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>&lt;/div&gt;
      &lt;ul className={flowStyles.printList}&gt;<span style="color: rgba(0, 0, 0, 1)">
          {
            nodes.map((x) </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> (
            </span>&lt;li key={x.id} className={flowStyles.printItem}&gt;
                &lt;span className={flowStyles.printItemTitle}&gt;{x.data.label}&lt;/span&gt;
                &lt;span className={flowStyles.printItemTips}&gt;({x.type})&lt;/span&gt;
            &lt;/li&gt;<span style="color: rgba(0, 0, 0, 1)">
            ))
          }
      </span>&lt;/ul&gt;
      &lt;/div&gt;
    &lt;/div&gt;<span style="color: rgba(0, 0, 0, 1)">
);
}</span></span></pre>
</div>
<p><span style="font-size: 14px">上面还通过&nbsp;<span style="color: rgba(128, 0, 0, 1)"><strong>useStoreState</strong></span> 拿到了画布上的节点信息 nodes,该 nodes 基于 Redux 管理,无需手动更新</span></p>
<hr>
<p><span style="font-size: 14px">&nbsp;</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">接着处理&nbsp;<span style="color: rgba(128, 0, 0, 1)"><strong>onDragOver</strong></span> 事件,更新&nbsp;dropEffect,和&nbsp;effectAllowed 保持一致</span></p>
<p><span style="font-size: 14px">然后在&nbsp;<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 实例提供的&nbsp;project 方法处理为 ReactFlow 坐标系</span></p>
<p><span style="font-size: 14px">最后组装节点信息,更新 elements 即可</span>&nbsp;</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) =&gt;<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>) =&gt;<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>=&gt;<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>) =&gt;<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>&lt;div className={flowStyles.graph} <span style="color: rgba(0, 0, 255, 1)">ref</span>={graphWrapper}&gt;
      &lt;<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>&gt;
      &lt;Controls /&gt;
      &lt;/ReactFlow&gt;
    &lt;/div&gt;<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">&nbsp;</span></p>
<p><span style="font-size: 14px">&nbsp;</span></p>
<p><span style="font-size: 16px"><strong>三、连线</strong></span></p>
<p><span style="font-size: 14px">在画布上连线的时候,会触发 ReactFlow&nbsp;<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 来添加连线,这个方法接收两个参数&nbsp;<strong><span style="color: rgba(128, 0, 0, 1)">edgeParams</span></strong> 和&nbsp;<strong><span style="color: rgba(128, 0, 0, 1)">elements</span></strong>,最后返回全新的&nbsp;<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> =&gt; setElements(els =&gt; 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>
    &lt;<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>
    /&gt;<span style="color: rgba(0, 0, 0, 1)">
);
}</span></span></pre>
</div>
<p><span style="font-size: 14px">如果需要设置连线类型,或者设置其他连线的信息,都可以通过&nbsp;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>的样式,可以使用&nbsp;<span style="color: rgba(0, 128, 128, 1)"><strong>connectionLineComponent</strong></span>,具体可以参考官方示例</span></p>
<p><span style="font-size: 14px">另外,还可以通过&nbsp;<span style="color: rgba(0, 128, 128, 1)"><strong>onEdgeUpdate</strong></span> 来更改连线的起点或终点,参考官方示例</span></p>
<p><span style="font-size: 14px">&nbsp;</span></p>
<p><span style="font-size: 14px">&nbsp;</span></p>
<p><span style="font-size: 16px"><strong>四、获取画布数据</strong></span></p>
<p><span style="font-size: 14px">在最开始的 index.jsx 中维护了一份 ReactFlow 的画布实例&nbsp;reactFlowInstance,并传给了 Graph 和 Toolbar</span></p>
<p><span style="font-size: 14px">通过&nbsp;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(() =&gt;<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>&lt;div className={flowStyles.toolbar}&gt;
      &lt;<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>&gt;<span style="color: rgba(0, 0, 0, 1)">
      保存
      </span>&lt;/button&gt;
    &lt;/div&gt;<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>&nbsp;Instance.toObject</strong></span>,拿到的是画布的全量数据,如果只需要 elements 可以使用<span style="color: rgba(128, 0, 0, 1)"><strong>&nbsp;Instance.getElements</strong></span></span></p>
<p><span style="font-size: 14px">完整的实例方法可以参考官方文档</span></p>
<p><span style="font-size: 14px">&nbsp;</span></p>
<p><span style="font-size: 14px">除了通过实例获取画布数据,还可以使用&nbsp;<span style="color: rgba(128, 0, 0, 1)"><strong>useStoreState</strong></span>&nbsp;</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 = () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">const</span> nodes = useStoreState((state) =&gt;<span style="color: rgba(0, 0, 0, 1)"> state.nodes);
</span><span style="color: rgba(0, 0, 255, 1)">const</span> edges = useStoreState((state) =&gt;<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 = () =&gt;<span style="color: rgba(0, 0, 0, 1)"> (
</span>&lt;ReactFlow elements={elements}&gt;
    &lt;NodesDebugger /&gt;
&lt;/ReactFlow&gt;<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">具体使用哪种方式,可以根据实际的业务场景来取舍&nbsp;</span></p>
<hr>
<p><span style="font-size: 14px">&nbsp;</span></p>
<p><span style="font-size: 14px">&nbsp;</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>&nbsp;flow-demo-app</span></p>
<p>&nbsp;</p><br><br>
来源:https://www.cnblogs.com/wisewrong/p/15433647.html
頁: [1]
查看完整版本: React Flow 实战(二)—— 拖拽添加节点