轻量化流程图开发,比 X6 清爽太多 —— React Flow 实战(一)

react

需求千千万,流程图常在

没想到多年以后,我再次遇到一个关于流程图开发的需求

以前少不更事,头铁用 GG-Editor 搞了一次流程图《在 React 项目中引入 GG-Editor 编辑可视化流程》,差点把自己给埋了

这次再遇到类似的需求,在各路大神的指点下,我选择了 React Flow 来进行开发,原因如下:

1. 相比于 jsPlumb、Antv/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

然后调用组件,传入 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

这部分内容会在后面的文章中介绍~

以上是 轻量化流程图开发,比 X6 清爽太多 —— React Flow 实战(一) 的全部内容, 来源链接: utcz.com/z/383618.html

回到顶部