提供使用 React Flow 构建基于节点的 UI 的架构指导,适用于设计基于流程的应用程序,在状态管理、交互和性能等方面进行决策。
React Flow Architecture 1: When to User React Flow. 良好,视觉编程接口.是一项面向实际任务的技能,主要用于Workflow 构建器和自动化工具.;Diagram 编辑器(flowcharts, org charts).;
从功能定位来看,该技能强调把分散的操作要求整理成清晰、可复用的处理流程,使用户能够围绕既定目标快速准备输入、选择执行方式并获得结构化结果。实际使用前应先确认任务范围、数据来源、运行环境、必要权限和关键参数,再依据技能说明逐步执行;若输入条件不完整,应先补齐信息或采用保守配置,避免因错误假设导致结果偏离需求。
执行过程中需要关注工具调用是否成功、接口或依赖是否可用、输出格式是否符合预期,并对异常提示、缺失字段和边界情况进行处理;涉及批量任务时,还应保存进度,避免中断后重复操作。该技能适合用于一次性任务,也可以接入自动化工作流,与其他技能或上层代理配合完成更完整的业务链路;在组合使用时,应明确每一步的输入输出关系,并避免不同步骤之间出现参数冲突。
在锁定技术栈或启动冲刺开发前,请运行以下检查序列。仅针对一次性原型可跳过。
明确交互行为 — 列出核心用户操作(例如:拖拽、连接、删除、分组)。通过条件:每个操作均对应一个具体可实现的 React Flow 回调函数(如 onNodesChange、onConnect 等)。
评估规模等级 — 预估峰值节点数(可见画布内或整个文档总量)。通过条件:该数值落在“节点数量指南”表格某一行范围内,且你接受该行所推荐的策略(例如:当匹配该行时启用 onlyRenderVisibleElements)。
确定状态存放位置 — 选择本地 Hook、外部 Store,或 Redux 等方案。通过条件:用一句话明确说明持久化、撤销/重做或跨界面同步的状态将存于何处;或明确声明“暂不需要”。
再次审视替代方案 — 若当前用例落入“建议考虑替代方案”的范畴,通过条件:用一句话说明为何仍选用 React Flow,或明确指出你已选用列表中哪一替代方案。
@xyflow/system (vanilla TypeScript) ├── 核心算法(边路径、边界计算、视口管理) ├── xypanzoom(基于 d3 的平移/缩放) ├── xydrag、xyhandle、xyminimap、xyresizer └── 共享类型定义 @xyflow/react(依赖 @xyflow/system) ├── React 组件与 Hook ├── 基于 Zustand 的状态管理 Store └── 框架专属集成 @xyflow/svelte(依赖 @xyflow/system) └── Svelte 组件与 Store
含义:核心逻辑与框架无关。在贡献代码或调试问题时,请先确认问题是出现在 @xyflow/system 还是框架专属包中。
// 使用 useNodesState / useEdgesState 快速原型开发 const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
优势:简洁,样板代码最少 劣势:状态局限于组件树内部
// Zustand Store 示例
import { create } from 'zustand';
interface FlowStore {
nodes: Node[];
edges: Edge[];
setNodes: (nodes: Node[]) => void;
onNodesChange: OnNodesChange;
}
const useFlowStore = create((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 ;
}
优势:状态全局可访问,更易于持久化与同步 劣势:配置成本更高,需谨慎优化 selector
// 通过 selector 接入
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: 'Child' },
},
];
注意事项:
extent: 'parent' 限制拖拽范围expandParent: true 启用父节点自动展开// 保存视口状态
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 加载数据
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 替代方案 |
| 受控模式 | 非受控模式 |
|---|---|
| 样板代码更多 | 代码量更少 |
| 完全掌控状态 | 内部维护状态 |
| 持久化更简单 | 需调用 toObject() |
| 更适合复杂应用 | 适合快速原型 |
| 严格模式(默认) | 宽松模式 |
|---|---|
| 仅允许 source handle → target handle | 任意 handle → 任意 handle |
| 行为可预测 | 灵活性更高 |
| 适用于数据流建模 | 适用于通用图表 |
| 默认边 | 自定义边 |
|---|---|
| 渲染速度快 | 控制粒度更细 |
| 样式定制能力有限 | 支持任意 SVG/HTML 内容 |
| 适用于简单用例 | 适用于复杂标签等高级需求 |