轻量化流程图开发,比 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