轻量化流程图开发,比 X6 清爽太多 —— React Flow 实战(一)
需求千千万,流程图常在
没想到多年以后,我再次遇到一个关于流程图开发的需求
以前少不更事,头铁用 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