輕量化流程圖開發,比 X6 清爽太多 —— React Flow 實戰(一)


需求千千萬,流程圖常在

沒想到多年以后,我再次遇到一個關於流程圖開發的需求

以前少不更事,頭鐵用 GG-Editor 搞了一次流程圖《在 React 項目中引入 GG-Editor 編輯可視化流程》,差點把自己給埋了

這次再遇到類似的需求,在各路大神的指點下,我選擇了 React Flow 來進行開發,原因如下:

1. 相比於 jsPlumbAntv/X6 而言,React Flow 的技術相對先進

  // 小聲BB,X6 居然用到了 jquery: https://github.com/antvis/X6/blob/master/packages/x6/package.json#L70

2. 高度自定義,任何 ReactElement 都可以作為節點

3. API 真的超級簡單,而且體積不大,npm 3.9 MB

 

 

一、快速上手

首先在項目中安裝依賴

yarn add react-flow-renderer

注意,這里的 reat-flow 版本是 9.x,更高級的版本可能存在 API 差異,參考《Migrate to v10

然后調用組件,傳入 elements 就能渲染出一個流程圖

import React from "react"; import ReactFlow from "react-flow-renderer"; const elements = [ // node
 { id: "1", data: { label: 'Node 1', }, position: { x: 250, y: 25 }, }, { id: "2", data: { label: 'Node 2', }, position: { x: 100, y: 125 }, }, { id: "3", data: { label: 'Node 3', }, position: { x: 250, y: 250 }, }, // edge
  { id: "e1-2", source: "1", target: "2" }, { id: "e2-3", source: "2", target: "3" }, ]; export default function Demo() { return ( <div style={{ height: 300 }}>
      <ReactFlow elements={elements} />
    </div> ); }

這里的 elements 是一個包含節點 node 連線 edge 的對象數組,他們在數據結構上有以下特點:

 

node

- id: string  唯一標識,用於連線,必填

position: { x: number, y: number }  定位信息,必填

- type: string  定義節點的類型,可以是 React Flow 提供的 'default' | 'input' | 'output',也可以是自定義類型

- data: {} 傳入節點內的數據,根據實際的節點類型 type 傳入

// 完整的配置項可以查看官網 Node Options

每一個節點都必須含有一個 id 和 postion,自定義節點必須傳入 type

 

edge:

- id: string  唯一標識,必填

source: string  連線的起始節點的 id,必填

- target: string  連線的結束節點的 id,必填

- type: string  線的類型,React Flow 提供了貝塞爾曲線 bezier直線 straight折線 step帶圓角的折線 smoothstep,也支持自定義連線 

// 完整的配置項可以查看官網 Edge Options

線就很好理解,只需要起點 source 和終點 target 就能完成連線

 

React Flow 還提供了兩個工具方法來判斷 elements 中的元素是節點還是連線

import { isNode, isEdge } from 'react-flow-renderer';

掌握了“點”與“線”的基本概念,流程圖就能信手拈來

但產品經理可不會認同 React Flow 提供的默認節點類型,所以自定義節點就成了必修課

 

 

二、自定義節點

在上面介紹的 node 數據中,可以傳入一個 data,這個 data 會傳入節點組件中的 props

假如我們需要做一個這樣的節點

可以先寫按圖寫一個這樣的 ReactNode

import React from "react"; import { isArray } from "lodash"; // data 會從 elements 數據源傳入
const ListNode = ({ data }) => { const { title, list } = data || {}; return ( <div className="flow-node list-node">
      <div className="list-node_title">
        <span className="list-node_title__inner">{title}</span>
      </div>
      <ul className="list-node_content"> { isArray(list) && list.map((x, i) => ( <li className="list-node__item" key={i}>
              <span className="list-node__item_label">{x.name}</span>
              <span className="list-node__item_type">{x.type}</span>
            </li> )) } </ul>
    </div> ); }; export default React.memo(ListNode);

通過傳入的 data 就能渲染出這個節點的樣式,接下來解決連線的問題

 

ReactFlow 節點的連線是通過 Handle 組件實現的

Handle 其實就是節點上的“連接點”,需要多少個連接點,就可以在組件里寫多少個 <Handle />

它可以接收的 props 參數有:

type:  string 連接點類型,可選值為 出口 'source' | 入口 'target'必填

position:  string 連接點的位置,有四個可選值 'left' | 'right' | 'top' | 'bottom'

style: object 連接點的樣式,除了常見的寬高、顏色之外,還可以通過定位對 position 進行微調

id: string 如果節點中存在存在多個 source Handle 或者多個 target Handle, 可以通過 id 來精准控制連線的起點和終點

isConnectable: boolean 是否允許連線,可以從節點的 props 中獲取

 

比如節點左上角的連接點,就可以這么寫:

import React from "react"; import { Handle } from "react-flow-renderer"; const nodeBaseStyle = { background: "#0FA9CC", width: '8px', height: '8px', }; const nodeLeftTopStyle = { ...nodeBaseStyle, top: 60, }; const ListNode = ({ data, isConnectable = true }) => { return ( <div className="flow-node list-node">
      <div className="list-node_title"> {/* ... */} </div>
      <ul className="list-node_content"> {/* ... */} </ul>
      <Handle type="target" position="left" id="lt" style={nodeLeftTopStyle} isConnectable={isConnectable} />
    </div> ); }; export default React.memo(ListNode);

其他的節點也是以同樣的方式添加,注意定義好 type,因為連線只能從 source 連接到 target


 

現在自定義節點已經開發好了,在使用的時候需要先注冊,也就是向 <ReactFlow /> 傳入一個 nodeTypes

然后在使用的時候,需要在 elements 中聲明節點 node 的類型,以及連線 edge 的起點和終點 

const elements = [ // nodes
 { id: '1', // 聲明節點類型
    type: "list",
// data 會作為 props 傳給節點 data: { title:
'節點-1', list: [], }, isConnectable: true, position: { x: 220, y: 65 }, }, { id: '2', // 聲明節點類型 type: "list",
// data 會作為 props 傳給節點 data: { title:
'節點-2', list: [], }, isConnectable: true, position: { x: 395, y: 260 }, }, // edges { id: "egde1-2", type: "step", // 起始節點 id source: "1", // 起點 Handle id sourceHandle: "b", // 結束節點 id target: "2", // 終點 Handle id targetHandle: "lt", }, ];

 

 

三、自定義連線

ReactFlow 提供的默認連線可以設置 label

// 圖示流程圖的完整示例可以參考這里

 

但 label 只能設置文本,如果要在連線中間加一個按鈕,就需要自定義連線

ReactFlow edge 是通過 svg 繪制的,所以自定義連線本身也是一個 <path />

為了更方便的繪制 path,ReactFlow 提供了一些工具方法

import { // 繪制貝塞爾曲線
 getBezierPath, // 繪制帶圓角的折線
 getSmoothStepPath, // 計算出連線的中點
 getEdgeCenter, // 繪制連線末端的箭頭
 getMarkerEnd, } from "react-flow-renderer";

通過這些方法,就能很方便的繪制出自定義連線

const CustomEdge = ({ id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, borderRadius = 0, style = {}, data, arrowHeadType, markerEndId, }) => { const edgePath = getSmoothStepPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition, borderRadius, }); const markerEnd = getMarkerEnd(arrowHeadType, markerEndId); return ( <>
      <path id={id} style={style} className="custom-edge" d={edgePath} markerEnd={markerEnd} />
    </> ); }

接下來是連線中點的按鈕,為了在 svg 里添加 button,就需要使用 foreignObject

再通過 getEdgeCenter 獲取到連線的中點,就可以繪制按鈕了

const foreignObjectSize = 24; const CustomEdge = ({ id, sourceX, sourceY, targetX, targetY, }) => { const [edgeCenterX, edgeCenterY] = getEdgeCenter({ sourceX, sourceY, targetX, targetY, }); const onEdgeClick = (evt, id) => { evt.stopPropagation(); console.log(`click ${id}`); }; return ( <>
      <path />
      <foreignObject width={foreignObjectSize} height={foreignObjectSize} x={edgeCenterX - foreignObjectSize / 2} y={edgeCenterY - foreignObjectSize / 2} className="custom-edge-foreignobject"
      >
        <button onClick={(event) => onEdgeClick(event, id)} />
      </foreignObject>
    </> ); }

// 完整代碼可以查看官方的 Edge with Button 示例

 

和節點的 nodeTypes 一樣,自定義的連線也需要通過 edgeTypes 來注冊

並且在 elements 中需要設置對應的連線類型

const elements = [ // node // ... // edges
 { id: "egde1-2", type: "link", // 使用自定義連線
    source: "1", target: "2", }, ]

 

 

掌握了自定義節點和自定義連線之后,就能隨意的繪制流程圖了

但上面傳給 <ReactFlow /> 的 elements 都是一開始寫好的假數據

如果要開發一個真實的流程圖,肯定需要數據交互,這就需要用到 ReactFlowProvider

這部分內容會在后面的文章中介紹~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM