zoukankan      html  css  js  c++  java
  • 轻量化流程图开发,比 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

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

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

     

  • 相关阅读:
    C语言与内存模型初探
    【编辑中】软件工程知识萃取
    【深入理解计算机系统01】不同层级程序指令间的转换
    【统计学中的普适智慧】假设检验
    windows 内部预览版与迅雷极速版不配合
    网络安全理论初涉
    Unix philosophy
    BOP 2016 复赛题目
    10.8做题——USACO1.2命名那个数字(Name That Number)
    10.6上课——problem1切割木板(USACO 2006 November Gold)
  • 原文地址:https://www.cnblogs.com/wisewrong/p/15396697.html
Copyright © 2011-2022 走看看