React Flow使用指南(一)

1,920 阅读3分钟

1. 对比

React Flow 更侧重于在 React 生态系统中创建交互式节点图,而 X6 是一个更通用的图编辑引擎,支持多种前端框架,功能更为强大和灵活。如果你正在开发一个基于 React 的项目,并且需要快速实现交互式节点图,如流程图、工作流图等,React Flow 是一个很好的选择。

React Flow

  • 轻量级,核心功能聚焦于流程图、工作流设计
  • 提供基础节点/连线拖拽、连接点(Handles)、撤销重做、子流程图等。
  • 插件扩展能力较弱,适合中小复杂度场景。
  • 示例简洁,上手成本低。

X6

  • 功能全面,支持流程图、ER图、拓扑图、BPMN等复杂场景。
  • 高级特性:自定义节点/连线规则、动画、键盘快捷键、网格布局、对齐线、历史记录管理等。
  • 内置 Stencil(侧边栏组件库)、Clipboard(跨画布复制粘贴)、Transform(画布变换)等工具。
  • 支持 JSON 序列化和 GraphML 格式导入导出。

2. React Flow流程图使用

基本操作

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

const initialNodes = [
  { id: '1', position: { x: 0, y: 0 }, data: { label: 'Node 1' } },
  { id: '2', position: { x: 200, y: 0 }, data: { label: 'Node 2' } },
]; // 初始Nodes数据定义

const initialEdges = [{ id: 'e1-2', source: '1', target: '2' }]; // 初始Edges数据定义
function Flow0() {
  // 使用ReactFlow内部hooks创建数据以及数据流转处理方法
  const [nodes, setNodes, onNodesChange] = useNodesState<Node>(initialNodes);
  const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>(initialEdges);

  const onConnect = useCallback(
    (params: any) => setEdges(eds => addEdge(params, eds)),
    [setEdges],
  );
  return (
    <div style={{ width: '100vw', height: '100vh' }}>
      <ReactFlow
        maxZoom={1.1}
        fitView
        nodes={nodes}
        edges={edges}
        onConnect={onConnect}
        onNodesChange={onNodesChange}
        onEdgesChange={onEdgesChange}
      />
    </div>
  );
}

export default Flow0;

通过上面的代码,可以创建出一个含有两个节点、一条连线的基础流程图,同时具备基础交互:节点拖拽,节点之间建立连线,退格键删除连线等。

⚠️注意事项:

  • 必须导入 React Flow 样式表。
  • <ReactFlow /> 组件必须封装在具有宽度和高度的元素中。

受控模式

使用官方提供的 useNodesStateuseEdgesState 方法,获得的返回值中含有完整的“响应事件 -> 处理当前数据 -> 返回新状态”的流程。

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

onNodeChange 中会接收到混合了多种类型的变更的数组,如节点删除、节点选中、节点位置变更、节点尺寸变化等,onEdgesChange 同理。

The onNodesChange callback takes an array of NodeChange objects that you should use to update your flow's state. The NodeChange type is a union of six different object types that represent that various ways an node can change in a flow.

export type NodeChange<NodeType extends NodeBase = NodeBase> = NodeDimensionChange | NodePositionChange | NodeSelectionChange | NodeRemoveChange | NodeAddChange<NodeType> | NodeReplaceChange<NodeType>;
export type NodeRemoveChange = {
    id: string;
    type: 'remove';
};
...
// 处理节点变更
const onNodesChange = useCallback(
    (changes) => {
      setNodes((prevNodes) => 
        changes.reduce((acc, change) => {
          switch (change.type) {
            case 'remove':
              return acc.filter((node) => node.id !== change.id);
            ...
            ...
            default:
              return acc;
          }
        }, prevNodes)
      );
    },
    []
);

同时,对于部分变更需要特殊处理时,ReactFlow还提供了applyNodeChanges 函数,可手动筛选变更类型,批量处理变更。

import { applyNodeChanges } from 'reactflow'const [nodes, setNodes] = useState([]);

const onNodesChange = useCallback((changes) => {
  setNodes((prevNodes) => {
    // 先处理自定义变更
    const customChanges = changes.filter(change => change.type === 'add');
    const customUpdatedNodes = customUpdateChanges(customChanges, prevNodes)

    // 再用 applyNodeChanges 处理剩余变更
    const standardChanges = changes.filter(change => change.type !== 'add');
    return applyNodeChanges(standardChanges, customUpdatedNodes);
  });
}, []);

自定义节点/边

如果默认UI样式或者节点label不满足我们的需求,如下: 可以通过Node节点提供的type以及data字段,自定义节点渲染。

export type NodeBase<NodeData,NodeType> = {  
  id: string  
  data: NodeData   
  type?: NodeType
  ...
}
function RoundedCornersNode({data}: {data: {num: number}}) {
  return (
    <div
      style={{
        background: '#d9e9fa',
        borderRadius: '4px',
        minWidth: 100,
        display: 'flex',
        justifyContent: 'center',
        alignItems: 'center',
        height: 40,
      }}
    >
    // Handle 用于渲染节点间添加连线的把柄
      <Handle type="target" position={Position.Top} />
      {data.num}
      <Handle type="source" position={Position.Bottom} />
    </div>
  );
}

const nodeTypes = {
  rounded: RoundedCornersNode,
};

<ReactFlow nodeTypes={nodeTypes} .../>

下一篇会写如何实现拖放新增节点