第一章:ReactFlow 简介与核心概念
1.1 什么是 ReactFlow
| 概念名称 | 说明 | 注意事项 |
|---|
| ReactFlow | 一个基于 React 的开源库,用于构建可交互的流程图、节点图和数据流图。 | 需要 React 16.8+ 和 React DOM 支持;推荐使用函数组件和 Hook。 |
| 开源协议 | MIT 许可证,允许自由使用、修改和分发。 | 商业项目中使用无需授权,但建议保留版权说明。 |
| 核心能力 | 提供可拖拽节点、可连接边、缩放平移视图、自定义样式与交互等核心功能。 | 所有交互默认启用,可通过 props 精细控制。 |
| 社区与生态 | 拥有活跃的 GitHub 社区,支持 TypeScript,提供多种官方扩展(如 mini-map)。 | 建议关注官方文档和 GitHub 仓库以获取最新特性与更新日志。 |
1.2 核心概念解析:节点(Node)、边(Edge)、视图(Viewport)
节点(Node)
| 概念名称 | 说明 | 注意事项 |
|---|
| Node | 流程图中的基本单元,代表一个操作、状态或数据实体。 | 每个节点必须具有唯一 id 和 type,并定义其在画布上的位置(position)。 |
| id | 节点的唯一标识符,用于连接、更新和删除操作。 | 不可重复,建议使用字符串或数字。 |
| type | 节点的类型,决定其渲染方式(如 ‘default’、‘input’、‘output’ 或自定义)。 | 决定使用内置样式或自定义组件渲染。 |
| position | 节点在画布中的坐标,格式为 { x: number, y: number }。 | 坐标基于画布左上角原点,受视口缩放和平移影响。 |
| data | 存储节点相关数据的对象,用于传递标签、状态或其他业务信息。 | 可自由扩展,常用于自定义节点中显示文本或绑定事件。 |
| source / target | 指定该节点是否可作为边的起点或终点(连接点)。 | 若节点不可连接,需显式设置 source: false 或 target: false。 |
边(Edge)
| 概念名称 | 说明 | 注意事项 |
|---|
| Edge | 连接两个节点的线,表示数据流、依赖关系或控制流。 | 必须指定 source 和 target 节点的 id。 |
| id | 边的唯一标识符。 | 建议格式为 e-${source}-${target} 以避免冲突。 |
| source | 起始节点的 id。 | 必须对应画布中已存在的节点。 |
| target | 目标节点的 id。 | 必须对应画布中已存在的节点。 |
| type | 边的类型,如 ‘default’、‘smoothstep’、‘step’、‘straight’。 | 影响边的渲染样式和路径计算方式。 |
| animated | 是否显示动画效果(如流动光效)。 | 常用于表示激活状态或数据流动。 |
| label | 显示在边上的文本标签。 | 支持字符串或 React 元素。 |
| markerEnd | 指定边末端的箭头样式(如箭头类型、颜色)。 | 需配合 <Marker> 组件或内联样式使用。 |
视图(Viewport)
| 概念名称 | 说明 | 注意事项 |
|---|
| Viewport | 画布的可视区域,支持缩放和平移操作。 | 所有节点和边都绘制在视口坐标系中。 |
| zoom | 当前缩放级别,1.0 表示原始大小,>1 放大,<1 缩小。 | 默认范围通常为 0.1 到 4,可通过 minZoom/maxZoom 限制。 |
| pan | 视口的平移偏移量,格式为 { x: number, y: number }。 | 正值表示向右和向下移动。 |
| transform | 视口的变换矩阵,格式为 [x, y, zoom]。 | 可通过 setTransform 方法手动设置。 |
| fitView | 自动缩放和平移,使所有节点适配画布。 | 常用于初始化或重置视图。 |
1.3 ReactFlow 的应用场景与优势
| 应用场景 | 说明 | 优势 |
|---|
| 工作流设计器 | 构建可视化任务编排系统,如 CI/CD 流程、审批流程。 | 支持拖拽、连接、状态保存,易于与后端集成。 |
| 数据流图与 ETL 工具 | 展示数据处理流程,如数据清洗、转换、加载。 | 可视化数据流向,便于调试和优化。 |
| 图形化编程环境 | 如低代码平台、逻辑编排器(类似 Node-RED)。 | 支持自定义节点和逻辑绑定,扩展性强。 |
| 网络拓扑图 | 展示服务器、设备或服务之间的连接关系。 | 支持大规模节点渲染和交互。 |
| 决策树与状态机 | 可视化业务规则、状态转换逻辑。 | 便于非技术人员理解复杂逻辑。 |
| AI/ML 模型可视化 | 展示神经网络结构、训练流程。 | 可结合自定义组件展示模型层和参数。 |
| 优势 | 说明 | 注意事项 |
|---|
| 响应式与高性能 | 基于 React Fiber,支持大量节点渲染,性能优化良好。 | 需合理使用 React.memo 避免重渲染。 |
| 类型安全 | 完整的 TypeScript 支持,提供类型定义。 | 推荐在 TS 项目中使用以提升开发效率。 |
| 高度可定制 | 支持自定义节点、边、连接点、样式和交互逻辑。 | 自定义组件需遵循 ReactFlow 的渲染机制。 |
| 丰富的内置功能 | 内置缩放、平移、连接、选择、删除、迷你地图、背景网格等。 | 可通过 props 快速启用或禁用功能。 |
| 活跃的社区与文档 | 官方文档详尽,GitHub 示例丰富,社区响应及时。 | 建议定期查看更新日志以获取新特性。 |
第二章:环境搭建与快速上手
2.1 安装 ReactFlow 依赖
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| npm install | npm install reactflow | 使用 npm 安装 ReactFlow 核心库 | npm install reactflow | 需确保项目已安装 React 和 React DOM(版本 >= 16.8) |
| yarn add | yarn add reactflow | 使用 yarn 安装 ReactFlow 核心库 | yarn add reactflow | 推荐使用 yarn 的项目采用此方式 |
| 安装样式文件 | import 'reactflow/dist/style.css'; | 引入 ReactFlow 默认样式 | import ReactFlow from 'reactflow'; import 'reactflow/dist/style.css'; | 必须引入,否则节点、边等组件将无默认样式 |
| TypeScript 支持 | npm install @types/reactflow | 安装类型定义(ReactFlow v10+ 可选) | npm install @types/reactflow | ReactFlow v10+ 已内置类型,通常无需单独安装 |
| 安装布局插件 | npm install @reactflow/layout | 安装布局算法支持(如 Dagre) | npm install @reactflow/layout | 按需安装,用于自动排列节点 |
2.2 创建第一个流程图(Hello World 示例)
| 概念名称 | 说明 | 注意事项 |
|---|
| 初始化节点 | 定义至少一个节点对象,包含 id、type、position 和 data | 节点必须有唯一 id 和 position,否则无法渲染 |
| 初始化边 | 定义边对象,连接两个节点 | 边的 source 和 target 必须对应存在的节点 id |
| ReactFlow 组件 | 核心组件,接收 nodes 和 edges 作为 props 并渲染流程图 | 必须包裹在具有固定高度的容器中,否则无法显示 |
| 函数组件结构 | 使用 React 函数组件和 useState 管理 nodes 和 edges 状态 | 推荐使用函数组件 + Hook 模式 |
2.3 基本组件结构:ReactFlow、Controls、Background、MiniMap
ReactFlow
| 方法/属性名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| nodes | <ReactFlow nodes={nodes} edges={edges} /> | 传入节点数组 | const nodes = [{ id: '1', type: 'input', data: { label: 'Hello' }, position: { x: 100, y: 100 } }]; | 节点数组必须符合 Node 类型结构 |
| edges | <ReactFlow nodes={nodes} edges={edges} /> | 传入边数组 | const edges = [{ id: 'e1-2', source: '1', target: '2' }]; | 边数组必须符合 Edge 类型结构 |
| onConnect | onConnect={(params) => addEdge(params, edges)} | 处理节点连接事件 | import { addEdge } from 'reactflow'; | 需配合 ConnectionLineType 和连接点使用 |
| connectionLineType | connectionLineType="smoothstep" | 设置连接线类型 | 可选值:‘default’, ‘straight’, ‘step’, ‘smoothstep’ | 影响连接预览线样式 |
| fitView | fitView | 自动缩放和平移以适配所有节点 | <ReactFlow ... fitView /> | 常用于初始化后自动居中所有节点 |
| defaultViewport | defaultViewport={{ x: 0, y: 0, zoom: 1.5 }} | 设置初始视口变换 | 控制初始缩放和平移位置 | 可覆盖 fitView 效果,建议二选一 |
Controls
| 属性/方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| zoomIn | <Controls /> 或 <Controls showZoom={true} /> | 提供缩放按钮 | <Controls /> | 默认显示缩放+/-按钮和定位按钮 |
| zoomOut | 同上 | 缩小视图 | | |
| fitView | 同上 | 使所有节点适配画布 | | 点击后触发 reactFlowInstance.fitView() |
| position | position="top-right" | 设置控件位置 | 可选值:‘top-left’, ‘top-right’, ‘bottom-left’, ‘bottom-right’ | 默认为 ‘top-left’ |
Background
| 属性/方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| variant | variant="dots" 或 variant="lines" | 设置背景网格样式 | <Background variant="dots" gap={20} /> | ’dots’ 为点状,‘lines’ 为线状 |
| gap | gap={20} | 网格间距(像素) | | 数值越大,网格越稀疏 |
| size | size={1} | 网格线或点的大小 | | 通常与 gap 配合使用 |
| color | color="#aaa" | 网格颜色 | | 推荐使用浅色以避免干扰主图 |
MiniMap
| 属性/方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| nodeColor | nodeColor={(node) => '#ff0072'} | 自定义节点在迷你图中的颜色 | 可根据 node.type 或 node.data 动态返回颜色 | 接收 node 对象并返回颜色字符串 |
| nodeStrokeColor | nodeStrokeColor={() => '#fff'} | 设置节点边框颜色 | | 增强节点轮廓 visibility |
| maskColor | maskColor="rgba(255, 0, 0, 0.2)" | 设置视口遮罩颜色 | | 遮罩表示当前主视图可见区域 |
| viewable | viewable | 仅显示可滚动区域内的节点 | <MiniMap viewable /> | 提升性能,避免渲染不可见节点 |
第三章:节点(Nodes)详解
3.1 节点的基本结构与属性
| 属性名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| id | id: string | 节点唯一标识 | { id: '1', type: 'input', position: { x: 100, y: 100 }, data: { label: 'Start' } } | 必须为字符串,不可重复 |
| type | type: string | 节点类型,决定渲染方式 | type: 'default' | 'input' | 'output' | 'custom' | 内置类型有 ‘input’, ‘default’, ‘output’;自定义需配合自定义组件使用 |
| position | position: { x: number, y: number } | 节点在画布中的坐标 | position: { x: 200, y: 150 } | 基于画布左上角原点,受视口变换影响 |
| data | data: object | 存储业务数据,传递给自定义节点 | data: { label: 'Process', status: 'running' } | 常用于显示标签或绑定事件 |
| style | style: CSSProperties | 自定义节点内联样式 | style: { background: '#ffcc00', border: '2px solid #000' } | 覆盖默认样式,优先级高于 className |
| className | className: string | 添加 CSS 类名 | className: 'highlighted-node' | 用于配合外部 CSS 文件进行样式定制 |
| sourcePosition | sourcePosition: Position.Left/Right/Top/Bottom | 指定作为源时的连接点方向 | sourcePosition: Position.Right | 配合 Handle 组件使用,影响边的起点方向 |
| targetPosition | targetPosition: Position.Left/Right/Top/Bottom | 指定作为目标时的连接点方向 | targetPosition: Position.Left | 影响边的终点方向 |
| draggable | draggable: boolean | 是否可拖拽 | draggable: false | 设为 false 后节点不可拖动 |
| selectable | selectable: boolean | 是否可被选中 | selectable: false | 影响点击选择行为 |
| deletable | deletable: boolean | 是否可通过 Delete 键删除 | deletable: false | 需与 onDelete 事件配合使用 |
| hidden | hidden: boolean | 是否隐藏节点 | hidden: true | 隐藏后不渲染,但仍存在于节点数组中 |
3.2 内置节点类型与自定义节点
| 类型名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| input | type: 'input' | 表示流程输入节点 | { id: '1', type: 'input', position: {x:0,y:0}, data: {label: 'Input'}} | 通常用作流程起点 |
| default | type: 'default' | 普通处理节点 | { id: '2', type: 'default', ... } | 无特殊样式,常用于中间步骤 |
| output | type: 'output' | 表示流程输出节点 | { id: '3', type: 'output', ... } | 通常用作流程终点 |
| 自定义节点 | type: 'custom' | 使用自定义 React 组件渲染 | 定义 CustomNode 组件,并在 nodeTypes 中注册 | 必须通过 nodeTypes prop 注册组件 |
| 方法/属性 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| nodeTypes | nodeTypes={{ custom: CustomNode }} | 注册自定义节点组件 | <ReactFlow nodeTypes={nodeTypes} nodes={nodes} ... /> | key 为 type 名,value 为 React 组件 |
| Handle | <Handle type="source" position={Position.Right} /> | 添加连接点 | 在自定义节点组件中使用 | type 为 ‘source’ 或 ‘target’,position 指定方向 |
3.3 节点的交互行为:拖拽、选择、删除
| 方法/属性 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| onNodeDragStart | onNodeDragStart={(event, node) => {}} | 节点开始拖拽时触发 | onNodeDragStart={(e, n) => console.log('Drag start:', n.id)} | event 为原生事件,node 为当前拖拽节点 |
| onNodeDrag | onNodeDrag={(event, node) => {}} | 拖拽过程中持续触发 | onNodeDrag={(e, n) => updateNodePosition(n)} | 可用于实时更新位置或验证拖拽合法性 |
| onNodeDragEnd | onNodeDragEnd={(event, node) => {}} | 拖拽结束时触发 | onNodeDragEnd={(e, n) => saveNodePosition(n)} | 通常用于持久化节点位置 |
| onNodeClick | onNodeClick={(event, node) => {}} | 点击节点时触发 | onNodeClick={(e, n) => setSelectedNode(n)} | 可用于选中节点或弹出编辑面板 |
| onNodeDoubleClick | onNodeDoubleClick={(event, node) => {}} | 双击节点时触发 | onNodeDoubleClick={(e, n) => openEditModal(n)} | 常用于打开编辑对话框 |
| onDelete | onDelete={() => {}} | 当选中节点并按下 Delete 键时触发 | onDelete={() => deleteSelectedNodes()} | 需确保节点 deletable: true,且未被禁用 |
3.4 动态添加与更新节点
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| setNodes | setNodes((nodes) => [...nodes, newNode]) | 添加新节点 | setNodes((nds) => nds.concat({ id: '4', type: 'default', position: {x:300,y:300}, data: {label: 'New'} })) | 使用函数式更新确保基于最新状态 |
| setNodes(更新) | setNodes((nodes) => nodes.map(n => n.id === '1' ? {...n, data: {...n.data, label: 'Updated'}} : n)) | 更新节点属性 | 更新节点 label 或 position | 不可直接修改原数组,必须返回新数组 |
| setNodes(删除) | setNodes((nodes) => nodes.filter(n => n.id !== '1')) | 删除节点 | 删除 id 为 ‘1’ 的节点 | 配合 onNodesDelete 可监听删除事件 |
| useReactFlow | const { setNodes, getNodes } = useReactFlow() | 在组件外获取操作方法 | const { setNodes } = useReactFlow(); setNodes([...]) | 必须在 ReactFlow 子组件中使用 |
| getNodes | const nodes = getNodes() | 获取当前所有节点 | const currentNodes = getNodes() | 返回最新节点数组,可用于计算或验证 |
第四章:边(Edges)详解
4.1 边的基本结构与属性
| 属性名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| id | id: string | 边的唯一标识 | { id: 'e1-2', source: '1', target: '2' } | 建议格式 e-${source}-${target} |
| source | source: string | 起始节点 id | source: '1' | 必须对应存在的节点 |
| target | target: string | 目标节点 id | target: '2' | 必须对应存在的节点 |
| type | type: string | 边的类型 | type: 'smoothstep' | 影响渲染样式,见 4.2 节 |
| animated | animated: boolean | 是否显示动画效果 | animated: true | 常用于表示激活或数据流动 |
| label | label: string | ReactNode | 显示在边上的标签 | label: '数据流' 或 label: <div>标签</div> | 支持字符串或 JSX |
| style | style: CSSProperties | 自定义边样式 | style: { stroke: 'red', strokeWidth: 2 } | 可修改颜色、线宽等 |
| className | className: string | 添加 CSS 类名 | className: 'error-edge' | 用于配合 CSS 定制样式 |
| markerStart | markerStart: string | 起点标记(如箭头) | markerStart: 'url(#arrow)' | 需预先定义 SVG marker |
| markerEnd | markerEnd: string | 终点标记 | markerEnd: 'url(#arrow)' | 常用 url(#arrow) 或 url(#circle) |
| updatable | updatable: boolean | 'start' | 'end' | 是否可更新连接 | updatable: 'end' | 允许拖动边的起点或终点重新连接 |
| focusable | focusable: boolean | 是否可被键盘聚焦 | focusable: true | 影响可访问性 |
| deletable | deletable: boolean | 是否可通过 Delete 键删除 | deletable: false | 默认 true,设为 false 可防止误删 |
4.2 边的类型:默认边、贝塞尔边、步骤边、直线边、平滑步骤边
| 类型名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| default | type: 'default' | 默认贝塞尔曲线边 | type: 'default' | 平滑曲线,起点终点带切线方向 |
| smoothstep | type: 'smoothstep' | 平滑转角的步骤边 | type: 'smoothstep' | 路径为折线但转角圆滑,适合水平/垂直布局 |
| step | type: 'step' | 直角步骤边 | type: 'step' | 路径为严格直角折线 |
| straight | type: 'straight' | 直线边 | type: 'straight' | 两点间直线连接 |
| bezier | type: 'bezier' | 贝塞尔曲线边(同 default) | type: 'bezier' | 与 default 相同,保留别名 |
4.3 边的交互:连接、删除、标记(Markers)
| 方法/属性 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| onConnect | onConnect={(params) => addEdge(params, edges)} | 处理连接事件 | import { addEdge } from 'reactflow'; | addEdge 工具函数自动生成边对象 |
| onEdgeClick | onEdgeClick={(event, edge) => {}} | 点击边时触发 | onEdgeClick={(e, ed) => setSelectedEdge(ed)} | 可用于选中边或弹出编辑菜单 |
| onEdgeDoubleClick | onEdgeDoubleClick={(event, edge) => {}} | 双击边时触发 | onEdgeDoubleClick={(e, ed) => openEdgeEditor(ed)} | 常用于编辑边标签或属性 |
| onEdgesDelete | onEdgesDelete={(edges) => {}} | 当边被删除时触发 | onEdgesDelete={(eds) => removeFromState(eds)} | 接收被删除的边数组 |
| markerEnd | markerEnd: 'url(#arrow)' | 设置终点箭头 | 需配合 <Marker> 或内联定义 | 常用箭头类型:‘arrow’, ‘arrowclosed’, ‘circle’ |
| connectionLineType | connectionLineType="straight" | 设置连接预览线类型 | connectionLineType="smoothstep" | 影响拖拽连接时的预览线样式 |
4.4 自定义边组件
| 方法/属性 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| edgeTypes | edgeTypes={{ 'custom-edge': CustomEdge }} | 注册自定义边组件 | <ReactFlow edgeTypes={edgeTypes} edges={edges} ... /> | key 为 type 名,value 为 React 组件 |
| BaseEdge | <BaseEdge path="M0,0 L100,100" /> | 渲染基础边路径 | 用于构建自定义边的底层路径 | 接收 SVG path 字符串 |
| EdgeText | <EdgeText x={50} y={50} label="文本" /> | 在边上显示文本 | 常用于自定义边中显示 label | 可自定义位置和样式 |
| getBezierPath | getBezierPath({ sourceX, sourceY, targetX, targetY }) | 生成贝塞尔路径 | const [path, labelX, labelY] = getBezierPath(opts); | 用于自定义边中计算标准曲线路径 |
| getStraightPath | getStraightPath(...) | 生成直线路径 | 类似 getBezierPath,用于直线边 | 参数结构相同 |
| getSimpleBezierPath | getSimpleBezierPath(...) | 生成简化贝塞尔路径 | 适用于简单连接 | |
第五章:连接与交互控制
5.1 使用 Connectors 实现节点连接
| 概念/组件 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| Handle | <Handle type="source" position={Position.Right} id="handle-1" /> | 定义节点上的连接点(Connector) | 在自定义节点组件中使用,允许从该点连接或接收边 | type 必须为 ‘source’ 或 ‘target’;position 控制方向(Top/Right/Bottom/Left) |
| type | type: 'source' | 'target' | 指定连接点类型 | <Handle type="source" position={Position.Right} /> | source 表示可连线出发,target 表示可接收连接 |
| position | position: Position.Left/Right/Top/Bottom | 指定连接点所在边 | position={Position.Right} | 影响连接线的起始/终止方向 |
| id | id: string | 连接点唯一标识 | <Handle type="source" id="output" position={Position.Right} /> | 当一个节点有多个连接点时必须设置 id,用于精确匹配 sourceHandle/targetHandle |
| isConnectable | isConnectable: boolean | 0 | 1 | 是否可连接 | <Handle type="source" isConnectable={false} /> | 设为 false 后该点不可拖出连接线 |
| style | style: CSSProperties | 自定义连接点样式 | <Handle style={{ background: 'red', width: 10 }} /> | 可调整大小、颜色等 |
| className | className: string | 添加 CSS 类名 | <Handle className="custom-handle" /> | 用于配合外部 CSS 文件定制外观 |
说明:Handle 是实现节点连接的核心组件,需嵌入在节点组件内部。用户通过拖拽 source 类型的 Handle 到另一个节点的 target 类型 Handle 上来创建边。
5.2 连接限制与连接验证(isValidConnection)
| 方法/属性 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| isValidConnection | isValidConnection={(connection) => boolean} | 验证连接是否合法 | isValidConnection={(conn) => conn.source !== conn.target} | 返回 true 允许连接,false 拒绝 |
| connection | { source, target, sourceHandle, targetHandle } | 传递给 isValidConnection 的参数对象 | isValidConnection={({ source, target }) => source !== target} | 可用于检查节点类型、连接点 id、数据属性等 |
| 禁止自环连接 | — | 防止节点连接自身 | isValidConnection={({ source, target }) => source !== target} | 最常见的验证规则之一 |
| 限制连接类型 | — | 按节点类型限制连接 | isValidConnection={({ source, target }) => getNode(source).type !== 'output'} | 结合 getNodes() 或状态数据进行判断 |
| 限制连接点 id | — | 确保特定 handle 才能连接 | isValidConnection={({ sourceHandle }) => sourceHandle === 'output'} | 用于多端口节点的精确控制 |
| 限制目标唯一性 | — | 防止一个 target 被多次连接 | isValidConnection={({ target }) => !edges.some(e => e.target === target)} | 结合当前 edges 状态进行验证 |
提示:isValidConnection 在用户拖拽连接线时实时调用,可用于动态控制连接行为,提升用户体验。
5.3 处理连接事件(onConnect)
| 方法/属性 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| onConnect | onConnect={(connection) => {}} | 连接成功时触发 | onConnect={(conn) => setEdges((eds) => addEdge(conn, eds))} | connection 对象包含 source, target, sourceHandle, targetHandle |
| addEdge | addEdge(connection, edges) | 工具函数:根据连接生成边并添加 | import { addEdge } from 'reactflow'; | 自动生成唯一 id,返回新边对象 |
| 自定义边属性 | — | 在添加边时附加自定义数据 | onConnect={(conn) => setEdges([...edges, { ...conn, id: genId(), animated: true }])} | 可设置 animated, label, style 等属性 |
| 防止重复连接 | — | 避免相同 source 和 target 多次连接 | onConnect={(conn) => { if (!edges.some(e => e.source === conn.source && e.target === conn.target)) { ... } }} | 在 onConnect 中进行逻辑判断 |
| 异步处理 | — | 延迟添加边(如需服务器确认) | onConnect={async (conn) => { const ok = await validateOnServer(conn); if (ok) setEdges(eds => addEdge(conn, eds)); }} | 可结合异步验证逻辑 |
5.4 启用/禁用拖拽与选择
| 方法/属性 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| nodesDraggable | nodesDraggable={false} | 全局禁用所有节点拖拽 | <ReactFlow nodesDraggable={false} ... /> | 优先级高于单个节点的 draggable 属性 |
| nodesSelectable | nodesSelectable={false} | 全局禁用节点选择 | <ReactFlow nodesSelectable={false} ... /> | 点击节点不会被选中 |
| elementsSelectable | elementsSelectable={false} | 禁用所有元素(节点+边)选择 | <ReactFlow elementsSelectable={false} ... /> | 包含节点和边的选择行为 |
| edgesFocusable | edgesFocusable={false} | 边是否可被聚焦 | <ReactFlow edgesFocusable={false} ... /> | 影响键盘导航 |
| panOnDrag | panOnDrag={false} | 禁用拖拽画布平移 | <ReactFlow panOnDrag={false} ... /> | 设为 false 后鼠标拖拽不会移动画布 |
| panOnScroll | panOnScroll={false} | 禁用滚轮滚动平移 | <ReactFlow panOnScroll={false} ... /> | 滚轮仅用于缩放(若启用) |
| panOnScrollMode | panOnScrollMode="free" | "vertical" | "horizontal" | 设置滚轮平移模式 | panOnScrollMode="horizontal" | 需 panOnScroll={true} 时生效 |
| zoomOnScroll | zoomOnScroll={false} | 禁用滚轮缩放 | <ReactFlow zoomOnScroll={false} ... /> | 默认为 true |
| zoomOnPinch | zoomOnPinch={false} | 禁用双指缩放(触屏) | <ReactFlow zoomOnPinch={false} ... /> | 触屏设备适用 |
| zoomOnDoubleClick | zoomOnDoubleClick={false} | 禁用双击缩放 | <ReactFlow zoomOnDoubleClick={false} ... /> | 双击画布不再触发缩放 |
| preventScrolling | preventScrolling={false} | 允许画布外滚动 | <ReactFlow preventScrolling={false} ... /> | 设为 false 后,当鼠标在画布上时,页面仍可滚动 |
使用场景:
- 查看模式:设置
nodesDraggable={false}、nodesSelectable={false}、panOnDrag={false} 禁止交互。
- 专注编辑:禁用
zoomOnScroll 防止误操作。
- 移动端优化:调整
panOnPinch 和 preventScrolling 提升体验。
第六章:事件系统与状态管理
6.1 常用事件:onNodeDrag、onEdgeClick、onPaneClick 等
| 事件名称 | 语法 | 触发时机 | 代码示例 | 注意事项 |
|---|
| onNodeDragStart | (event, node) => void | 节点开始拖拽时 | onNodeDragStart={(e, n) => console.log('Drag start:', n.id)} | 可用于标记节点状态或初始化拖拽逻辑 |
| onNodeDrag | (event, node) => void | 拖拽过程中持续触发 | onNodeDrag={(e, n) => updatePreview(n)} | 高频触发,注意性能优化 |
| onNodeDragEnd | (event, node) => void | 节点拖拽结束时 | onNodeDragEnd={(e, n) => savePosition(n.id, n.position)} | 通常用于持久化节点位置 |
| onNodeClick | (event, node) => void | 点击节点时 | onNodeClick={(e, n) => setSelectedNode(n)} | 可用于选中节点或弹出编辑面板 |
| onNodeDoubleClick | (event, node) => void | 双击节点时 | onNodeDoubleClick={(e, n) => openEditor(n)} | 常用于打开配置对话框 |
| onNodeMouseEnter | (event, node) => void | 鼠标进入节点区域 | onNodeMouseEnter={(e, n) => highlightConnections(n)} | 可用于高亮相关边或显示提示 |
| onNodeMouseLeave | (event, node) => void | 鼠标离开节点区域 | onNodeMouseLeave={(e, n) => clearHighlights()} | 配合 onNodeMouseEnter 使用 |
| onEdgeClick | (event, edge) => void | 点击边时 | onEdgeClick={(e, ed) => setSelectedEdge(ed)} | 可用于查看边信息或编辑标签 |
| onEdgeDoubleClick | (event, edge) => void | 双击边时 | onEdgeDoubleClick={(e, ed) => editEdgeLabel(ed)} | 常用于快速编辑边属性 |
| onEdgeMouseEnter | (event, edge) => void | 鼠标进入边区域 | onEdgeMouseEnter={(e, ed) => setTooltip(ed.label)} | 可显示边的详细信息 |
| onEdgeMouseLeave | (event, edge) => void | 鼠标离开边区域 | onEdgeMouseLeave={() => setTooltip(null)} | 清除提示信息 |
| onPaneClick | (event) => void | 点击画布空白区域 | onPaneClick={() => clearSelection()} | 常用于取消所有选中状态 |
| onPaneContextMenu | (event) => void | 右键点击画布空白区域 | onPaneContextMenu={(e) => showContextMenu(e)} | 可弹出上下文菜单 |
| onConnect | (connection) => void | 成功建立连接时 | onConnect={(conn) => addEdge(conn, edges)} | 需配合 addEdge 工具函数使用 |
| onMove | (event, viewport) => void | 画布视口移动时(拖拽/缩放) | onMove={(e, v) => console.log('Viewport:', v)} | viewport 包含 x, y, zoom |
| onMoveEnd | (event, viewport) => void | 视口移动结束时 | onMoveEnd={(e, v) => saveViewport(v)} | 可用于保存视图状态 |
6.2 使用 useReactFlow Hook 管理全局状态
| 方法/属性 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| useReactFlow | const { setNodes, setEdges, getNodes, getEdges } = useReactFlow(); | 获取状态操作方法 | 在自定义组件中调用,无需通过 props 传递 | 必须在 <ReactFlow> 子组件中使用 |
| setNodes | setNodes((nodes) => [...nodes, newNode]) | 更新节点数组 | setNodes((nds) => nds.map(n => n.id === '1' ? {...n, selected: true} : n)) | 推荐使用函数式更新 |
| setEdges | setEdges((edges) => [...edges, newEdge]) | 更新边数组 | setEdges((eds) => eds.filter(e => e.id !== 'e1')) | 同样支持函数式更新 |
| setViewport | setViewport({ x, y, zoom }) | 设置画布视口位置和缩放 | setViewport({ x: 0, y: 0, zoom: 1 }) | 实现”居中”或”重置缩放”功能 |
| getViewport | const viewport = getViewport() | 获取当前视口状态 | const { x, y, zoom } = getViewport() | 返回 { x, y, zoom } 对象 |
| fitView | fitView({ padding, includeHiddenNodes }) | 自动缩放以适配所有节点 | fitView({ padding: 0.1 }) | 常用于初始化或重置视图 |
| fitBounds | fitBounds({ x, y, width, height }) | 缩放并平移以适配指定区域 | fitBounds({ x: 0, y: 0, width: 800, height: 600 }) | 需要手动计算边界 |
| project | project({ x, y }) | 将客户端坐标转换为画布坐标 | const pos = project({ x: e.clientX, y: e.clientY }); createNode(pos); | 用于在点击位置创建新节点 |
提示:useReactFlow 是管理 React Flow 全局状态的核心 Hook,避免了通过 props 层层传递状态更新函数。
6.3 获取当前元素(getNodes, getEdges)与更新状态
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| getNodes | const nodes = getNodes() | 获取当前所有节点 | const currentNodes = getNodes(); const inputNodes = currentNodes.filter(n => n.type === 'input'); | 返回最新节点数组,不受外部状态延迟影响 |
| getEdges | const edges = getEdges() | 获取当前所有边 | const activeEdges = getEdges().filter(e => e.animated); | 常用于计算、验证或导出流程结构 |
| getNode(id) | const node = getNode('1') | 根据 id 获取单个节点 | const startNode = getNode('start'); | 若节点不存在返回 null |
| getEdge(id) | const edge = getEdge('e1-2') | 根据 id 获取单个边 | const conn = getEdge('e1-2'); | 若边不存在返回 null |
| updateNode | setNodes(nodes.map(n => ...)) | 更新特定节点属性 | setNodes(nodes => nodes.map(n => n.id === '1' ? {...n, data: {...n.data, label: 'New'}} : n)) | 不可直接修改原对象,必须返回新数组 |
| updateEdge | setEdges(edges.map(e => ...)) | 更新特定边属性 | setEdges(edges => edges.map(e => e.id === 'e1-2' ? {...e, animated: true} : e)) | 同样需返回新数组 |
| addNodes | setNodes(nodes => [...nodes, newNode]) | 添加一个或多个节点 | setNodes(nodes => nodes.concat([{ id: '4', type: 'default', position: {x:100,y:100}, data: {label: 'Added'}}])) | 推荐使用 concat 或展开语法 |
| removeNodes | setNodes(nodes.filter(n => ...)) | 删除节点 | setNodes(nodes => nodes.filter(n => !selectedIds.includes(n.id))) | 可结合 onNodesDelete 事件监听 |
| removeEdges | setEdges(edges.filter(e => ...)) | 删除边 | setEdges(edges => edges.filter(e => e.source !== '1')) | 常用于清理与某节点相关的连接 |
6.4 自定义事件处理逻辑
| 场景 | 实现方式 | 代码示例 | 说明 |
|---|
| 组合操作 | 在事件中组合多个状态更新 | onNodeClick={(e, n) => { setSelectedNode(n); highlightConnectedEdges(n.id); }} | 提升交互体验,如点击节点同时高亮其连接 |
| 防抖处理 | 对高频事件(如拖拽)进行防抖 | const debouncedDrag = debounce((node) => saveToServer(node), 500); onNodeDragEnd={debouncedDrag} | 避免频繁请求服务器 |
| 撤销/重做 | 结合 useUndoRedo 或自定义栈管理 | 维护 history 栈,onNodeDragEnd, onConnect 时入栈,提供 undo() 函数 | 需自行实现或使用第三方库 |
| 权限控制 | 根据用户角色决定是否允许操作 | onNodeClick={(e, n) => { if (user.role === 'admin') editNode(n); }} | 实现细粒度的交互控制 |
| 连接前验证 | 在 onConnect 中调用 isValidConnection 逻辑 | onConnect={(conn) => { if (validateConnection(conn)) setEdges(eds => addEdge(conn, eds)); }} | 确保连接符合业务规则 |
| 自定义右键菜单 | onPaneContextMenu 弹出菜单,onNodeContextMenu 定制节点菜单 | onPaneContextMenu={(e) => showMenu('canvas', e)}; onNodeContextMenu={(e, n) => showMenu('node', e, n)} | 需配合状态管理显示/隐藏菜单 |
| 拖拽创建节点 | onDrop + useCallback + project 实现从外部拖入节点 | 参考官方示例:使用 onDrop 获取数据,project 转换坐标,setNodes 添加 | 常用于工具箱拖拽创建节点 |
| 自动布局 | onPaneClick 触发布局算法,setNodes 更新位置 | onPaneClick={() => { const layouted = applyLayout(nodes, edges); setNodes(layouted); }} | 可集成 dagre 等布局库 |
最佳实践:
- 事件处理函数应保持轻量,复杂逻辑抽离到单独函数。
- 使用
useCallback 包裹事件处理器,避免不必要的重新渲染。
- 结合
useState 和 useReactFlow 实现复杂状态管理。
- 利用
getNodes() 和 getEdges() 获取最新状态,避免闭包问题。
第七章:视图控制与布局
7.1 视口(Viewport)操作:缩放、平移、居中
| 操作 | 方法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| 获取当前视口 | getViewport() | const { x, y, zoom } = getViewport(); | 获取当前画布的平移和缩放状态 | console.log(getViewport()); | 返回 { x, y, zoom } 对象 |
| 设置视口 | setViewport(viewport) | setViewport({ x, y, zoom }) | 手动设置画布位置与缩放级别 | setViewport({ x: 100, y: 100, zoom: 1.5 }); | 直接控制视图状态 |
| 重置视图 | fitView() | fitView({ padding, includeHiddenNodes }) | 自动缩放并居中所有可见节点 | fitView({ padding: 0.1 }); | 常用于”重置视图”按钮 |
| 平移至指定位置 | setViewport() | setViewport({ x: 0, y: 0, zoom: 1 }) | 实现”居中”或”返回原点” | resetView = () => setViewport({ x: 0, y: 0, zoom: 1 }); | 需结合 fitView 或手动计算坐标 |
| 限制缩放范围 | minZoom, maxZoom | <ReactFlow minZoom={0.1} maxZoom={4} /> | 防止过度缩放 | <ReactFlow minZoom={0.2} maxZoom={2} /> | 默认值:minZoom=0.5, maxZoom=2 |
| 禁用平移 | panOnDrag | panOnDrag={false} | 锁定画布不可拖拽移动 | <ReactFlow panOnDrag={false} /> | 用于只读模式 |
| 禁用缩放 | zoomOnScroll, zoomOnPinch | zoomOnScroll={false} | 禁用滚轮或手势缩放 | <ReactFlow zoomOnScroll={false} zoomOnPinch={false} /> | 提升特定场景下的操作稳定性 |
提示:useReactFlow 提供的 setViewport 和 fitView 是控制视图的核心方法,常与按钮或快捷键结合使用。
7.2 使用 fitView 自动适配画布
| 参数 | 类型 | 默认值 | 说明 | 示例 |
|---|
| padding | number | 0.1 | 内容与画布边缘的留白比例 | fitView({ padding: 0.2 }) → 增加边距 |
| includeHiddenNodes | boolean | false | 是否包含隐藏节点在适配范围内 | fitView({ includeHiddenNodes: true }) |
| nodes | string[] | undefined | 指定仅适配某些节点(通过 id) | fitView({ nodes: ['node-1', 'node-2'] }) |
| duration | number | undefined | 动画持续时间(毫秒) | fitView({ duration: 800 }) → 平滑动画 |
使用场景
| 场景 | 实现方式 |
|---|
| 初始化自动居中 | useEffect(() => { fitView(); }, []); |
| 按钮触发适配 | <button onClick={() => fitView()}>Fit View</button> |
| 聚焦特定节点 | fitView({ nodes: ['start'], padding: 0.3 }); |
| 平滑动画效果 | fitView({ duration: 1000, padding: 0.1 }); |
注意:fitView 不会改变节点的实际位置,仅调整视口以最佳方式展示内容。
7.3 键盘快捷键配置
| 快捷键 | 默认行为 | 配置属性 | 禁用方法 | 自定义方式 |
|---|
| Ctrl + 滚轮 / Ctrl + 触摸板缩放 | 缩放画布 | zoomOnScroll, zoomOnPinch | zoomOnScroll={false} | — |
| 双击 | 缩放(默认双击放大) | zoomOnDoubleClick | zoomOnDoubleClick={false} | — |
| 空格键 + 拖拽 | 临时启用画布拖拽(即使 panOnDrag=false) | — | 无法禁用 | 可通过 CSS 或事件拦截覆盖 |
| Delete / Backspace | 删除选中节点或边 | deleteKeyCode | deleteKeyCode={null} | deleteKeyCode="Delete" |
| Ctrl + A | 全选所有节点(若 selectNodesOnDrag 启用) | — | 无直接禁用 | 需拦截键盘事件 |
| Ctrl + Z | 撤销(需自行实现) | — | — | 需结合状态管理实现 |
| F | 居中并适配所有节点(等效 fitView) | fitViewOnClick | fitViewOnClick={false} | 可重新绑定 |
自定义快捷键示例:
useEffect(() => {
const handleKey = (e) => {
if (e.code === 'KeyF' && e.ctrlKey) {
e.preventDefault();
fitView(); // Ctrl + F 居中
}
if (e.code === 'Equal' && e.ctrlKey) {
e.preventDefault();
setViewport(v => ({ ...v, zoom: v.zoom * 1.2 })); // Ctrl + + 放大
}
if (e.code === 'Minus' && e.ctrlKey) {
e.preventDefault();
setViewport(v => ({ ...v, zoom: v.zoom / 1.2 })); // Ctrl + - 缩小
}
};
window.addEventListener('keydown', handleKey);
return () => window.removeEventListener('keydown', handleKey);
}, [fitView, setViewport]);
建议:对于复杂快捷键系统,可使用 Mousetrap 或 hotkeys-js 等库进行管理。
7.4 使用布局算法(Dagre 等)自动排列节点
常用布局库
| 库名 | 用途 | 安装 | 备注 |
|---|
| dagre | 有向图自动布局(层级排列) | npm install dagre | 最常用,适合流程图、组织结构图 |
| cose-bilkent | 力导向布局(物理模拟) | npm install @falcon-js/cose-bilkent | 适合复杂网络图 |
| elkjs | Eclipse Layout Kernel,支持多种算法 | npm install elkjs | 功能强大,适合大型图 |
使用 Dagre 自动布局示例:
import dagre from 'dagre';
const dagreGraph = new dagre.graphlib.Graph();
dagreGraph.setDefaultEdgeLabel(() => ({}));
const nodeWidth = 172;
const nodeHeight = 36;
export function applyLayout(nodes, edges, direction = 'TB') {
dagreGraph.setGraph({ rankdir: direction, ranksep: 100, nodesep: 50 });
// 添加节点
nodes.forEach((node) => {
dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
});
// 添加边
edges.forEach((edge) => {
dagreGraph.setEdge(edge.source, edge.target);
});
// 执行布局
dagre.layout(dagreGraph);
// 更新节点位置
return nodes.map((node) => {
const nodeWithPosition = dagreGraph.node(node.id);
return {
...node,
position: {
x: nodeWithPosition.x - nodeWidth / 2,
y: nodeWithPosition.y - nodeHeight / 2,
},
};
});
}
调用方式:
<button onClick={() => {
const layoutedNodes = applyLayout(getNodes(), getEdges(), 'LR'); // 从左到右
setNodes(layoutedNodes);
}}>
Apply Layout
</button>
布局方向(rankdir):
| 值 | 方向 |
|---|
| ’TB’ | 上 → 下(默认) |
| ‘BT’ | 下 → 上 |
| ’LR’ | 左 → 右 |
| ’RL’ | 右 → 左 |
最佳实践:
- 布局前建议先
fitView() 以获得最佳视觉效果。
- 可结合 transition 动画实现平滑移动。
- 对于大型图,建议在 Web Worker 中执行布局算法避免阻塞 UI。
第八章:高级功能与自定义组件
8.1 自定义节点组件开发
| 属性/概念 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| 自定义节点注册 | nodeTypes={{ custom: CustomNode }} | 注册自定义节点类型 | <ReactFlow nodeTypes={nodeTypes} /> | CustomNode 为 React 组件 |
| 节点组件参数 | ({ id, data, selected, isConnectable }) | 传入自定义节点的属性 | function CustomNode({ data }) { return <div>{data.label}</div>; } | data 用于传递自定义数据 |
| 节点样式控制 | style, className | 自定义外观 | <div style={{ background: 'blue' }} className="my-node"> | 可结合 CSS 模块或 styled-components |
| 响应选择状态 | selected | 判断是否被选中 | {selected && <div className="selected-outline">} | 可用于显示选中边框或高亮 |
| 可连接性 | isConnectable | 控制是否可连接 | <Handle type="source" isConnectable={isConnectable} /> | 与全局 isConnectable 配置联动 |
| 动态内容渲染 | 条件渲染、循环等 React 语法 | 根据 data 渲染不同内容 | {data.type === 'input' && <InputIcon />} | 支持任意 React 逻辑 |
| 节点尺寸控制 | width, height | 固定或动态尺寸 | style={{ width: data.width }} | 需配合布局算法调整 |
8.2 自定义边组件开发
| 属性/概念 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| 自定义边注册 | edgeTypes={{ custom: CustomEdge }} | 注册自定义边类型 | <ReactFlow edgeTypes={edgeTypes} /> | CustomEdge 为 React 组件 |
| 边组件参数 | ({ id, source, target, sourceX, sourceY, targetX, targetY, ...rest }) | 提供边的几何信息 | 用于绘制路径或添加标签 | 包含坐标、箭头等信息 |
| 贝塞尔曲线边 | getBezierPath({ ... }) | 生成平滑曲线路径 | const path = getBezierPath({ sourceX, sourceY, targetX, targetY }); | 需从 reactflow 导入 |
| 直线边 | getSimpleBezierPath() 或自定义 SVG | 绘制直线 | <line x1={sourceX} y1={sourceY} x2={targetX} y2={targetY} /> | 更轻量 |
| 带箭头的边 | markerEnd 属性 | 添加箭头 | markerEnd="url(#reactflow__arrow)" | 需定义 SVG marker |
| 边标签(Label) | 绝对定位或相对计算 | 在边上显示文本 | <text x={(sourceX + targetX) / 2} y={...}>{data.label}</text> | 可点击、可编辑 |
| 交互支持 | onEdgeClick, onMouseEnter | 添加交互行为 | <path onClick={onEdgeClick} onMouseEnter={onMouseEnter} /> | 提升用户体验 |
8.3 使用 Handle 控制连接点
| 属性 | 类型 | 默认值 | 说明 | 示例 |
|---|
| type | 'source' | 'target' | required | 连接点类型 | <Handle type="source" /> |
| position | Position.Left/Right/Top/Bottom | Position.Right | 连接点方向 | position={Position.Bottom} |
| id | string | undefined | 唯一标识(多连接点时必需) | <Handle id="a" type="source" /> |
| isConnectable | boolean | 0 | 1 | true | 是否可连接 | isConnectable={false} |
| style | CSSProperties | {} | 自定义样式 | style={{ background: 'red', width: 10 }} |
| className | string | ” | 自定义类名 | className="custom-handle" |
| onConnect | ({ connection }) => void | undefined | 连接时回调 | onConnect={(params) => console.log(params)} |
| isValidConnection | (connection) => boolean | undefined | 自定义连接验证 | isValidConnection={(conn) => conn.target !== 'node-1'} |
8.4 支持 TypeScript 的类型定义
| 类型名称 | 定义 | 用途 | 示例 |
|---|
| Node<T> | import { Node } from 'reactflow'; | 定义节点类型 | type MyNode = Node<{ label: string; value: number; }, 'input'>; |
| Edge<T> | import { Edge } from 'reactflow'; | 定义边类型 | type MyEdge = Edge<{ label?: string; }>; |
| NodeType | React.ComponentType<NodeProps<T>> | 自定义节点组件类型 | const CustomNode: NodeType<{ label: string }> = ({ data }) => {...}; |
| EdgeType | React.ComponentType<EdgeProps<T>> | 自定义边组件类型 | const CustomEdge: EdgeType<{ label: string }> = ({ data }) => {...}; |
| Connection | 内置类型 | 连接对象结构 | onConnect={(conn: Connection) => ...} |
| Viewport | { x: number; y: number; zoom: number; } | 视口类型 | const vp: Viewport = getViewport(); |
| 泛型支持 | Node<Data, Type> | 精确约束 data 和 type | const nodes: Node<{ name: string }, 'user'>[] = [...]; |
| 自定义 Handle 类型 | 扩展 HandleProps | 类型安全的 Handle | interface MyHandleProps extends HandleProps { customProp?: boolean; } |
提示:使用 TypeScript 可显著提升开发体验,提供自动补全和编译时检查,减少运行时错误。建议在大型项目中启用。
第九章:性能优化与最佳实践
9.1 大规模节点渲染优化(React.memo、useCallback)
| 优化策略 | 实现方式 | 用途 | 代码示例 | 注意事项 |
|---|
| 节点组件 memo 化 | React.memo(CustomNode) | 防止节点不必要的重渲染 | const MemoizedNode = React.memo(CustomNode); | 仅当 props 变化时重新渲染 |
| 使用 useCallback | useCallback(() => {...}, [deps]) | 缓存事件处理器 | const onNodeClick = useCallback((e, n) => {...}, []); | 避免子组件因函数引用变化而重渲染 |
| 自定义节点 shouldUpdate | 结合 memo 和自定义比较 | 精确控制更新条件 | React.memo(Node, (prev, next) => prev.data.value === next.data.value) | 提升复杂节点性能 |
| 减少内联对象 | 避免在 JSX 中创建新对象 | 防止 React 认为 props 变化 | // ❌ <Node data={{ label: 'A' }} /> / // ✅ const data = useMemo(() => ({ label: 'A' }), []); | 尤其在 nodes 数组中 |
| 使用 Immutable 数据 | 配合 immer 或 immutable.js | 确保状态引用不变 | produce(draft => { draft.nodes[0].data.label = 'new'; }) | 便于 memo 比较 |
| 分离关注点 | 将 UI 与逻辑分离 | 降低组件复杂度 | 把事件处理抽到自定义 Hook | 提升可维护性 |
9.2 虚拟滚动与懒加载策略
| 策略 | 实现方式 | 适用场景 | 工具/方法 | 注意事项 |
|---|
| 虚拟滚动(Virtual Scrolling) | 仅渲染视口内节点 | 数千节点场景 | 自定义实现或使用 react-virtual | 需计算节点是否在视口内 |
| 懒加载节点 | 按需加载子图或分组 | 大型流程分步加载 | onNodeClick 加载关联节点 | 需设计数据加载逻辑 |
| 分页加载 | 分批加载节点 | 初始加载性能优化 | fetchNodes(page, size) | 配合分页 API |
| 动态注册节点类型 | 按需引入自定义节点 | 减少初始包体积 | React.lazy + Suspense | 适合插件化架构 |
| 节点池化(Pooling) | 复用节点实例 | 高频创建/销毁场景 | 手动管理节点对象池 | 复杂,一般不推荐 |
9.3 避免不必要的重渲染
| 问题 | 解决方案 | 示例 | 说明 |
|---|
| 父组件更新导致子组件重渲染 | 使用 React.memo | const Node = React.memo(({ data }) => {...}) | 仅当 data 变化时更新 |
| 内联函数导致引用变化 | useCallback 缓存函数 | const onClick = useCallback(fn, deps) | 防止传递新函数引用 |
| 状态提升过度 | 将状态下沉或使用局部状态 | 为节点维护局部编辑状态 | 避免全局状态频繁更新 |
| 频繁 setState | 批量更新或防抖 | setTimeout 或 debounce | 减少状态更新频率 |
| 使用 useFlow 获取最新状态 | 直接调用 getNodes() | 在事件中使用 getNodes() 而非依赖 props | 避免闭包陷阱 |
9.4 错误处理与调试技巧
| 技巧 | 方法 | 工具/代码 | 说明 |
|---|
| 错误边界(Error Boundary) | componentDidCatch | <ErrorBoundary><ReactFlow /></ErrorBoundary> | 捕获渲染异常,防止白屏 |
| 控制台日志 | console.log | onError={(err) => console.error(err)} | 监听 React Flow 内部错误 |
| React DevTools | 检查组件树 | 查看节点组件渲染情况 | 分析重渲染问题 |
| 性能分析 | React Profiler | Profiler 组件包装 | 定位性能瓶颈 |
| 节点/边数据验证 | 运行时校验 | if (!node.id) throw new Error(...) | 确保数据结构正确 |
| 边界情况处理 | 空状态、null 节点 | nodes?.map(...) 或默认值 | 提升健壮性 |
| 自定义 Hook 调试 | useDebugValue | 在自定义 Hook 中显示状态 | 便于调试状态逻辑 |
第十章:集成与扩展
10.1 保存与恢复流程图状态(序列化)
| 操作 | 方法 | 示例 | 说明 |
|---|
| 获取当前状态 | getNodes(), getEdges() | const flow = { nodes: getNodes(), edges: getEdges() }; | 获取最新数据 |
| 序列化为 JSON | JSON.stringify(flow) | localStorage.setItem('flow', JSON.stringify(flow)); | 用于保存 |
| 反序列化 | JSON.parse() | const saved = JSON.parse(localStorage.getItem('flow')); | 恢复前解析 |
| 保存到服务器 | fetch(‘/api/flow’, { method: ‘POST’, body: json }) | 发送 JSON 到后端 | 持久化存储 |
| 初始化加载 | setNodes, setEdges | useEffect(() => { setNodes(saved.nodes); }, []) | 页面加载时恢复 |
| 版本控制 | 添加 version 字段 | { version: '1.0', nodes: [...], edges: [...] } | 兼容未来格式变更 |
10.2 与状态管理库(Redux/Zustand)集成
| 库 | 集成方式 | 优势 | 示例 |
|---|
| Redux | 将 nodes/edges 存入 store | 中心化状态,易于调试 | dispatch(setFlow({ nodes, edges })); |
| Redux Toolkit | 使用 createSlice | 简化 Redux 写法 | flowSlice.actions.updateNodes(nodes) |
| Zustand | 创建全局 store | 轻量,无需 provider | const useFlowStore = create(...) => ({ nodes, edges, setNodes }) |
| Context API | 自定义 Context | 原生方案,适合中小型应用 | FlowContext.Provider value={state} |
| 与 useReactFlow 协同 | 从 store 获取数据 | 保持状态同步 | const { nodes } = useFlowStore(); |
10.3 与后端 API 通信
| 场景 | 实现方式 | 方法 | 注意事项 |
|---|
| 加载初始流程 | useEffect + fetch | GET /api/flows/:id | 错误处理和加载状态 |
| 保存流程 | onSave + fetch | PUT /api/flows/:id | 支持自动保存(定时或 onMoveEnd) |
| 实时协作 | WebSocket | socket.on('flow-update', updateFlow) | 需处理冲突合并 |
| 验证连接 | API 校验 | POST /api/validate-connection | 在 onConnect 中调用 |
| 执行流程 | 触发后端执行 | POST /api/flows/:id/run | 返回执行结果或日志 |
| 权限控制 | 请求头携带 token | headers: { Authorization: 'Bearer ...' } | 确保安全 |
10.4 打包与部署注意事项
| 事项 | 建议 | 说明 |
|---|
| 代码分割 | React.lazy 加载自定义节点 | 减少初始包体积 |
| Tree Shaking | 确保构建工具启用 | 剔除未使用代码 |
| CDN 托管依赖 | 将 reactflow 等库外链 | 加速加载 |
| 环境变量 | 区分 dev/prod API 地址 | process.env.REACT_APP_API_URL |
| 静态资源路径 | 配置 PUBLIC_URL | 正确加载图片、字体等 |
| PWA 支持 | 添加 manifest 和 service worker | 支持离线访问 |
| SEO 优化 | 服务端渲染(SSR)或预渲染 | 提升搜索引擎可见性(若需) |
| 监控 | 集成 Sentry 或 LogRocket | 捕获生产环境错误 |
| 性能监控 | Lighthouse 测试 | 确保加载和交互性能 |