React Flow Architecture

提供使用 React Flow 构建节点式 UI 的架构建议与最佳实践。

已扫描
适合谁
前端开发工程师、可视化工具架构师
不适合谁
仅需静态图表的初学者、无复杂交互需求的简单项目
国内可用性
需网络配置。可能需要网络配置或第三方服务可访问。
安装难度
新手友好(★☆☆)。基于终端操作、依赖、API Key 和本地环境要求的初步判断。

安装与下载

openclaw skills install @anderskev/react-flow-architecture

Skill 说明

命令、参数、文件名以原文为准

React Flow 架构

何时使用 React Flow

适合场景

  • 可视化编程界面
  • 工作流构建器与自动化工具
  • 图表编辑器(流程图、组织结构图)
  • 数据管道可视化
  • 思维导图工具
  • 基于节点的音视频编辑器
  • 决策树构建器
  • 状态机设计工具

建议考虑其他方案

  • 简单的静态图表(可直接使用 SVG 或 Canvas)
  • 高度实时协作场景(可能需要自定义同步层)
  • 3D 可视化(建议使用 Three.js 或 react-three-fiber)
  • 包含 10,000+ 节点的图分析(建议使用基于 WebGL 的解决方案,如 Sigma.js)

决策流程(决策门控)

在锁定技术栈或进入开发冲刺前执行以下序列。仅对临时原型可跳过。

  1. 明确交互行为 —— 列出主要用户操作(如拖拽、连接、删除、分组)。通过标准:每个操作都对应一个你将实现的具体 React Flow 回调函数(如 onNodesChangeonConnect 等)。
  1. 评估规模 —— 预估最大节点数量(可见画布或文档总量)。通过标准:你的估算范围匹配 [节点数量指南](#node-count-guidelines) 中的某一行,并接受该行推荐的策略(例如当指南建议使用 onlyRenderVisibleElements 时,应采纳该策略)。
  1. 确定状态存放位置 —— 选择本地 Hook、外部状态库或 Redux 等。通过标准:用一句话说明持久化、撤销或跨界面同步的状态存储位置,或明确“暂不需要”。
  1. 重新审视替代方案 —— 若使用场景符合 [建议考虑其他方案](#建议考虑其他方案) 中的描述,通过标准:用一句话解释为何仍选择 React Flow,或说明已选用哪个列出的替代方案。

架构模式

包结构(xyflow)

@xyflow/system (原生 TypeScript)
├── 核心算法(边路径、边界框、视口计算)
├── xypanzoom (基于 D3 的缩放/平移)
├── xydrag, xyhandle, xyminimap, xyresizer
└── 共享类型定义

@xyflow/react (依赖 @xyflow/system)
├── React 组件与 Hook
├── Zustand 状态管理仓库
└── 框架特定集成

@xyflow/svelte (依赖 @xyflow/system)
└── Svelte 组件与仓库

含义:核心逻辑与框架无关。在贡献或排查问题时,需判断问题是出在 @xyflow/system 还是特定框架包中。

状态管理方案

1. 本地状态(简单应用)

// 用于原型开发
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);

优点:简单,样板代码少

缺点:状态局限于组件树内

2. 外部状态(生产环境)

// Zustand 示例
import { create } from 'zustand';

interface FlowStore {
  nodes: Node[];
  edges: Edge[];
  setNodes: (nodes: Node[]) => void;
  onNodesChange: OnNodesChange;
}

const useFlowStore = create<FlowStore>((set, get) => ({
  nodes: initialNodes,
  edges: initialEdges,
  setNodes: (nodes) => set({ nodes }),
  onNodesChange: (changes) => {
    set({ nodes: applyNodeChanges(changes, get().nodes) });
  },
}));

// 在组件中使用
function Flow() {
  const { nodes, edges, onNodesChange } = useFlowStore();
  return <ReactFlow nodes={nodes} onNodesChange={onNodesChange} />;
}

优点:状态全局可访问,便于持久化与同步

缺点:配置更复杂,需注意选择器优化

3. Redux 或其他状态库

// 通过选择器连接
const nodes = useSelector(selectNodes);
const dispatch = useDispatch();

const onNodesChange = useCallback((changes: NodeChange[]) => {
  dispatch(nodesChanged(changes));
}, [dispatch]);

数据流架构

用户输入 → 变更事件 → Reducer/处理器 → 状态更新 → 重新渲染
     ↓
[拖动节点] → onNodesChange → applyNodeChanges → setNodes → ReactFlow
     ↓
[连接节点] → onConnect → addEdge → setEdges → ReactFlow
     ↓
[删除节点] → onNodesDelete → deleteElements → setNodes/setEdges → ReactFlow

子流程模式(嵌套节点)

// 父节点包含子节点
const nodes = [
  {
    id: 'group-1',
    type: 'group',
    position: { x: 0, y: 0 },
    style: { width: 300, height: 200 },
  },
  {
    id: 'child-1',
    parentId: 'group-1',  // 关键:父节点引用
    extent: 'parent',      // 关键:限制在父节点范围内
    position: { x: 10, y: 30 },  // 相对于父节点的位置
    data: { label: '子节点' },
  },
];

注意事项

  • 使用 extent: 'parent' 实现拖拽范围限制
  • 使用 expandParent: true 可自动扩展父节点
  • 父节点的 z-index 影响子节点渲染顺序

视口状态持久化

// 保存视口状态
const { toObject, setViewport } = useReactFlow();

const handleSave = () => {
  const flow = toObject();
  // flow.nodes, flow.edges, flow.viewport
  localStorage.setItem('flow', JSON.stringify(flow));
};

const handleRestore = () => {
  const flow = JSON.parse(localStorage.getItem('flow'));
  setNodes(flow.nodes);
  setEdges(flow.edges);
  setViewport(flow.viewport);
};

集成模式

与后端/API 集成

// 从 API 加载数据
useEffect(() => {
  fetch('/api/flow')
    .then(r => r.json())
    .then(({ nodes, edges }) => {
      setNodes(nodes);
      setEdges(edges);
    });
}, []);

// 延迟自动保存
const debouncedSave = useMemo(
  () => debounce((nodes, edges) => {
    fetch('/api/flow', {
      method: 'POST',
      body: JSON.stringify({ nodes, edges }),
    });
  }, 1000),
  []
);

useEffect(() => {
  debouncedSave(nodes, edges);
}, [nodes, edges]);

与布局算法集成

import dagre from 'dagre';

function getLayoutedElements(nodes: Node[], edges: Edge[]) {
  const g = new dagre.graphlib.Graph();
  g.setGraph({ rankdir: 'TB' });
  g.setDefaultEdgeLabel(() => ({}));

  nodes.forEach((node) => {
    g.setNode(node.id, { width: 150, height: 50 });
  });

  edges.forEach((edge) => {
    g.setEdge(edge.source, edge.target);
  });

  dagre.layout(g);

  return {
    nodes: nodes.map((node) => {
      const pos = g.node(node.id);
      return { ...node, position: { x: pos.x, y: pos.y } };
    }),
    edges,
  };
}

性能扩展

节点数量建议

节点数量建议策略
少于 100 个使用默认设置
100 至 500 个启用 onlyRenderVisibleElements
500 至 1000 个简化自定义节点,减少 DOM 元素数量
超过 1000 个考虑虚拟化或 WebGL 替代方案

优化技巧

<ReactFlow
  // 仅渲染视口内的节点和边
  onlyRenderVisibleElements={true}

  // 减少节点圆角(提升相交计算性能)
  nodeExtent={[[-1000, -1000], [1000, 1000]]}

  // 禁用不需要的功能
  elementsSelectable={false}
  panOnDrag={false}
  zoomOnScroll={false}
/>

权衡取舍

受控与非受控模式

受控模式非受控模式
代码量较多代码更少
完全控制状态内部管理状态
易于持久化需调用 toObject()
适合复杂应用适合原型开发

连接模式

严格模式(默认)松散模式
仅支持源 → 目标连接任意连接点 → 任意连接点
行为可预测更灵活
适用于数据流场景适用于图表类场景
<ReactFlow connectionMode={ConnectionMode.Loose} />

边的渲染方式

默认边自定义边
渲染速度快更多控制权
样式有限支持任意 SVG 或 HTML
适用于简单场景适用于复杂标签需求
A
@anderskev

已收录 5 个 Skill

相关推荐