React-flow 工作流案例详解

嗨,大家好,我是徐小夕

之前一直在社区分享零代码&低代码的技术实践,也陆陆续续设计并开发了多款可视化搭建产品,比如:

上期和大家分享了我最近做的 React-Flow 中文文档. 到今天为止, 核心部分已经完全翻译完成. 大家可以直接使用中文文档快速学习和使用 React-Flow 搭建自己的工作流.

109a6853f50c15e13d50661ac823303b.png

github: https://github.com/MrXujiang/react-flow

文档地址: http://react-flow.com

接下来我会基于我写的中文文档, 带大家做一个非常有意思的工作流案例, 方便大家快速上手 React-Flow.

案例展示

29285377c7be09530c52891e5e3faa55.gif

这个案例主要包含几个技术点:

  • 如何自定义节点

  • 如何自定义边

  • 如何设置画布缩略图和画布控件

  • 如何实现嵌套节点

  • 如何设置画布样式

  • 如何拖拽框选多个节点

掌握了以上几点, 我们可以实现各种场景的流程图或者工作流. 上图的案例我已经推送到 github, 大家也可以下载代码参考学习.

自定义节点

因为官方提供的节点样式比较有限,所以我们需要自定义节点和节点样式. 上述我做的案例中有三个自定义节点:

  • 按钮节点

  • 图片节点

  • 图文标签节点(顶部根节点)

如下图所示:

97fceacb9ad3c1a6eac880a8e8cd02c6.png

具体的自定义节点的方式我在中文文档中也有详细介绍和 demo, 这里给大家分享一下实现方式:

function LogoNode({ data, isConnectable }) {
    const { src, text } = data;
    return (
      <div className="flow-logo">
        <Handle
            type="source"
            position={Position.Bottom}
            id="a"
            // style={handleStyle}
            isConnectable={isConnectable}
        />
        <Handle
            type="source"
            position={Position.Bottom}
            id="b"
            isConnectable={isConnectable}
        />
        <div>
          <img src={src} />
          <div className="flow-logo-text">{ text }</div>
        </div>
      </div>
    );
  }

做好之后我们只需要在 app 入口注册节点即可:

const nodeTypes = useMemo(() => ({ textUpdater: TextUpdaterNode }), []);
 
return <ReactFlow nodeTypes={nodeTypes} />;

是不是非常简单? 大家可以按照React-Flow 中文文档来学习更加复杂的自定义节点功能.

自定义边

自定义边自定义节点的方式类似, 我们先来看一下自定义边的案例:

db46c18d5f5fae67cc77383d77142d99.png

大家在网上看到的花里胡哨的思维导图, 流程图的连接线, 我们其实都可以用自定义边来实现:

import {
  BaseEdge,
  EdgeLabelRenderer,
  getStraightPath,
  useReactFlow,
} from '@xyflow/react';
 
export default function CustomEdge({ id, sourceX, sourceY, targetX, targetY }) {
  const { setEdges } = useReactFlow();
  const [edgePath] = getStraightPath({
    sourceX,
    sourceY,
    targetX,
    targetY,
  });
 
  return (
    <>
      <BaseEdge id={id} path={edgePath} />
      <EdgeLabelRenderer>
        <button
          onClick={() => setEdges((edges) => edges.filter((e) => e.id !== id))}
        >
          删除
        </button>
      </EdgeLabelRenderer>
    </>
  );
}

设置画布缩略图和画布控件

一般用过figma或者设计类软件的小伙伴可能比较熟悉画布控件缩略图的概念.

它们可以帮助我们更高效的浏览图表和进行更便捷的图表操作. 当然 react-flow 也提供了开箱即用的插件来实现.

96e2437e00ea25e2915818df94ae6c16.png

话不多说, 接下来我们就来看看具体的实现:

import { ReactFlow, MiniMap } from '@xyflow/react';

const defaultNodes = [
  {
    id: '1',
    type: 'input',
    data: { label: 'Dooring用户' },
    position: { x: 250, y: 25 },
    style: { backgroundColor: '#6ede87', color: 'white' },
  },

  {
    id: '2',
    // you can also pass a React component as a label
    data: { label: <div>Dooring零代码平台</div> },
    position: { x: 100, y: 125 },
    style: { backgroundColor: '#ff0072', color: 'white' },
  },
  {
    id: '3',
    type: 'output',
    data: { label: '发布页面' },
    position: { x: 250, y: 250 },
    style: { backgroundColor: '#6865A5', color: 'white' },
  },
];
const defaultEdges = [
  { id: 'e1-2', source: '1', target: '2' },
  { id: 'e2-3', source: '2', target: '3', animated: true },
];

const nodeColor = (node) => {
  switch (node.type) {
    case 'input':
      return '#6ede87';
    case 'output':
      return '#6865A5';
    default:
      return '#ff0072';
  }
};

function Flow() {
  return (
    <div style={{ width: '100%', height: '60vh' }}>
    <ReactFlow defaultNodes={defaultNodes} defaultEdges={defaultEdges} fitView>
      <MiniMap nodeColor={nodeColor} nodeStrokeWidth={3} zoomable pannable />
    </ReactFlow>
    </div>
    
  );
}

export default Flow;

通过上述代码我们就能实现一个非常简单的自定义缩略图的功能, 如下图所示:

5e9498cceeeb1907d4e213b154e7456a.png

如何实现嵌套节点

要想实现起嵌套节点的效果, 我们只需要调整节点结构, 即可轻松实现如下效果:

1edd7d521f1857bdfb2c124eea117dd4.png

如果要将一个节点添加为另一个节点的子节点,则需要使用 parentId(在以前的版本中称为parentNode)选项(您可以在节点选项部分找到所有选项的列表)。一旦我们这样做了,子节点就会相对于其父节点定位。 { x: 0, y: 0 } 的位置是父级的左上角。

代码案例如下:

import { useCallback, useState } from 'react';
import {
  ReactFlow,
  addEdge,
  applyEdgeChanges,
  applyNodeChanges,
  Background,
} from '@xyflow/react';

const initialNodes = [
  {
    id: 'A',
    type: 'group',
    data: { label: null },
    position: { x: 0, y: 0 },
    style: {
      width: 170,
      height: 140,
    },
  },
  {
    id: 'B',
    type: 'input',
    data: { label: 'Dooring Node' },
    position: { x: 10, y: 10 },
    parentId: 'A',
    extent: 'parent',
  },
  {
    id: 'C',
    data: { label: 'React Flow' },
    position: { x: 10, y: 90 },
    parentId: 'A',
    extent: 'parent',
  },
];

const initialEdges = [
  { id: 'b-c', source: 'B', target: 'C' }
];

const rfStyle = {
  backgroundColor: '#D0C0F7',
};

function Flow() {
  const [nodes, setNodes] = useState(initialNodes);
  const [edges, setEdges] = useState(initialEdges);

  const onNodesChange = useCallback(
    (changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
    [setNodes],
  );
  const onEdgesChange = useCallback(
    (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),
    [setEdges],
  );
  const onConnect = useCallback(
    (connection) => setEdges((eds) => addEdge(connection, eds)),
    [setEdges],
  );

  return (
    <div style={{width: '100%', height: '30vh'}}>
      <ReactFlow
        nodes={nodes}
        edges={edges}
        onNodesChange={onNodesChange}
        onEdgesChange={onEdgesChange}
        onConnect={onConnect}
        fitView
        style={rfStyle}
        attributionPosition="top-right"
      >
        <Background />
      </ReactFlow>
    </div>
  );
}

export default Flow;

如何拖拽框选多个节点

如果我们更喜欢 Figma/sketch/design 工具控件,可以设置panOnScroll={true}selectionOnDrag={true}

  • 平移:空格+拖动鼠标、滚动、鼠标中键或右键

  • 缩放:俯仰或 cmd + 滚动

  • 创建选区:拖动鼠标

这样就能实现类似多选框选的效果了:

e6224cc4587d12745c7f7d38d52d2018.gif

代码如下:

import { useCallback } from 'react';
import {
  ReactFlow,
  addEdge,
  useEdgesState,
  useNodesState,
  SelectionMode,
} from '@xyflow/react';

const initialNodes = [
  {
    id: '1',
    data: { label: 'Dooring' },
    position: { x: 150, y: 0 },
  },
  {
    id: '2',
    data: { label: 'Nest-Admin' },
    position: { x: 0, y: 150 },
  },
  {
    id: '3',
    data: { label: 'Next-Admin' },
    position: { x: 300, y: 150 },
  },
];

const initialEdges = [
  { id: 'e1-2', source: '1', target: '2' },
  { id: 'e1-3', source: '1', target: '3' },
];

function Flow() {
  const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
  const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);

  const onConnect = useCallback(
    (connection) => setEdges((eds) => addEdge(connection, eds)),
    [setEdges],
  );

  const panOnDrag = [1, 2];

  return (
    <div style={{width: '100%', height: '30vh'}}>
      <ReactFlow
        nodes={nodes}
        edges={edges}
        onNodesChange={onNodesChange}
        onEdgesChange={onEdgesChange}
        onConnect={onConnect}
        panOnScroll
        selectionOnDrag
        panOnDrag={panOnDrag}
        selectionMode={SelectionMode.Partial}
        fitView
      />
    </div>
    
  );
}

export default Flow;

文章最开头的案例的源码我已经上传 github 了, 大家感兴趣可以学习参考一下. 完整案例效果图:

400371973b26619084d0ff157d18f47c.png

技术交流

75449a249671ee43a5a217ae6fc5e531.jpeg

后期规划

大家对于文档有好的建议, 也欢迎随时和我反馈交流~

后面会在文档中加一些笔比较复杂的可视化 + 工作流案例, 供大家学习参考.

https://github.com/MrXujiang/react-flow

中文文档地址: http://react-flow.com

后续我也会持续迭代 H5-Dooring 零代码项目,让它成为最好用的可视化 + 无代码应用搭建工具,如果大家感兴趣,也随时欢迎留言区反馈交流~

4417c2713b3722d3af50fb8d2c1d8396.png

往期精彩

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值