React-flow 工作流案例详解

嗨,大家好,我是徐小夕

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

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

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

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

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

案例展示

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

  • 如何自定义节点

  • 如何自定义边

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

  • 如何实现嵌套节点

  • 如何设置画布样式

  • 如何拖拽框选多个节点

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

自定义节点

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

  • 按钮节点

  • 图片节点

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

如下图所示:

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

go 复制代码
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 入口注册节点即可:

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

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

自定义边

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

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

go 复制代码
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 也提供了开箱即用的插件来实现.

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

go 复制代码
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;

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

如何实现嵌套节点

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

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

代码案例如下:

go 复制代码
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 + 滚动

  • 创建选区:拖动鼠标

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

代码如下:

go 复制代码
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 了, 大家感兴趣可以学习参考一下. 完整案例效果图:

技术交流


后期规划

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

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

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

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

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

往期精彩

相关推荐
会飞的鱼先生12 分钟前
vue3 内置组件KeepAlive的使用
前端·javascript·vue.js
斯~内克26 分钟前
前端浏览器窗口交互完全指南:从基础操作到高级控制
前端
Mike_jia1 小时前
Memos:知识工作者的理想开源笔记系统
前端
前端大白话1 小时前
前端崩溃瞬间救星!10 个 JavaScript 实战技巧大揭秘
前端·javascript
loveoobaby1 小时前
Shadertoy着色器移植到Three.js经验总结
前端
蓝易云1 小时前
在Linux、CentOS7中设置shell脚本开机自启动服务
前端·后端·centos
浩龙不eMo1 小时前
前端获取环境变量方式区分(Vite)
前端·vite
一千柯橘1 小时前
Nestjs 解决 request entity too large
javascript·后端
土豆骑士1 小时前
monorepo 实战练习
前端
土豆骑士1 小时前
monorepo最佳实践
前端