Article

工作流 ReactFlow

更新于:2026-07-10

第一章: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: falsetarget: 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 installnpm install reactflow使用 npm 安装 ReactFlow 核心库npm install reactflow需确保项目已安装 React 和 React DOM(版本 >= 16.8)
yarn addyarn 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/reactflowReactFlow 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 类型结构
onConnectonConnect={(params) => addEdge(params, edges)}处理节点连接事件import { addEdge } from 'reactflow';需配合 ConnectionLineType 和连接点使用
connectionLineTypeconnectionLineType="smoothstep"设置连接线类型可选值:‘default’, ‘straight’, ‘step’, ‘smoothstep’影响连接预览线样式
fitViewfitView自动缩放和平移以适配所有节点<ReactFlow ... fitView />常用于初始化后自动居中所有节点
defaultViewportdefaultViewport={{ x: 0, y: 0, zoom: 1.5 }}设置初始视口变换控制初始缩放和平移位置可覆盖 fitView 效果,建议二选一

Controls

属性/方法名称语法用途代码示例注意事项
zoomIn<Controls /><Controls showZoom={true} />提供缩放按钮<Controls />默认显示缩放+/-按钮和定位按钮
zoomOut同上缩小视图
fitView同上使所有节点适配画布点击后触发 reactFlowInstance.fitView()
positionposition="top-right"设置控件位置可选值:‘top-left’, ‘top-right’, ‘bottom-left’, ‘bottom-right’默认为 ‘top-left’

Background

属性/方法名称语法用途代码示例注意事项
variantvariant="dots"variant="lines"设置背景网格样式<Background variant="dots" gap={20} />’dots’ 为点状,‘lines’ 为线状
gapgap={20}网格间距(像素)数值越大,网格越稀疏
sizesize={1}网格线或点的大小通常与 gap 配合使用
colorcolor="#aaa"网格颜色推荐使用浅色以避免干扰主图

MiniMap

属性/方法名称语法用途代码示例注意事项
nodeColornodeColor={(node) => '#ff0072'}自定义节点在迷你图中的颜色可根据 node.typenode.data 动态返回颜色接收 node 对象并返回颜色字符串
nodeStrokeColornodeStrokeColor={() => '#fff'}设置节点边框颜色增强节点轮廓 visibility
maskColormaskColor="rgba(255, 0, 0, 0.2)"设置视口遮罩颜色遮罩表示当前主视图可见区域
viewableviewable仅显示可滚动区域内的节点<MiniMap viewable />提升性能,避免渲染不可见节点

第三章:节点(Nodes)详解

3.1 节点的基本结构与属性

属性名称语法用途代码示例注意事项
idid: string节点唯一标识{ id: '1', type: 'input', position: { x: 100, y: 100 }, data: { label: 'Start' } }必须为字符串,不可重复
typetype: string节点类型,决定渲染方式type: 'default' | 'input' | 'output' | 'custom'内置类型有 ‘input’, ‘default’, ‘output’;自定义需配合自定义组件使用
positionposition: { x: number, y: number }节点在画布中的坐标position: { x: 200, y: 150 }基于画布左上角原点,受视口变换影响
datadata: object存储业务数据,传递给自定义节点data: { label: 'Process', status: 'running' }常用于显示标签或绑定事件
stylestyle: CSSProperties自定义节点内联样式style: { background: '#ffcc00', border: '2px solid #000' }覆盖默认样式,优先级高于 className
classNameclassName: string添加 CSS 类名className: 'highlighted-node'用于配合外部 CSS 文件进行样式定制
sourcePositionsourcePosition: Position.Left/Right/Top/Bottom指定作为源时的连接点方向sourcePosition: Position.Right配合 Handle 组件使用,影响边的起点方向
targetPositiontargetPosition: Position.Left/Right/Top/Bottom指定作为目标时的连接点方向targetPosition: Position.Left影响边的终点方向
draggabledraggable: boolean是否可拖拽draggable: false设为 false 后节点不可拖动
selectableselectable: boolean是否可被选中selectable: false影响点击选择行为
deletabledeletable: boolean是否可通过 Delete 键删除deletable: false需与 onDelete 事件配合使用
hiddenhidden: boolean是否隐藏节点hidden: true隐藏后不渲染,但仍存在于节点数组中

3.2 内置节点类型与自定义节点

类型名称语法用途代码示例注意事项
inputtype: 'input'表示流程输入节点{ id: '1', type: 'input', position: {x:0,y:0}, data: {label: 'Input'}}通常用作流程起点
defaulttype: 'default'普通处理节点{ id: '2', type: 'default', ... }无特殊样式,常用于中间步骤
outputtype: 'output'表示流程输出节点{ id: '3', type: 'output', ... }通常用作流程终点
自定义节点type: 'custom'使用自定义 React 组件渲染定义 CustomNode 组件,并在 nodeTypes 中注册必须通过 nodeTypes prop 注册组件
方法/属性语法用途代码示例注意事项
nodeTypesnodeTypes={{ custom: CustomNode }}注册自定义节点组件<ReactFlow nodeTypes={nodeTypes} nodes={nodes} ... />key 为 type 名,value 为 React 组件
Handle<Handle type="source" position={Position.Right} />添加连接点在自定义节点组件中使用type 为 ‘source’ 或 ‘target’,position 指定方向

3.3 节点的交互行为:拖拽、选择、删除

方法/属性语法用途代码示例注意事项
onNodeDragStartonNodeDragStart={(event, node) => {}}节点开始拖拽时触发onNodeDragStart={(e, n) => console.log('Drag start:', n.id)}event 为原生事件,node 为当前拖拽节点
onNodeDragonNodeDrag={(event, node) => {}}拖拽过程中持续触发onNodeDrag={(e, n) => updateNodePosition(n)}可用于实时更新位置或验证拖拽合法性
onNodeDragEndonNodeDragEnd={(event, node) => {}}拖拽结束时触发onNodeDragEnd={(e, n) => saveNodePosition(n)}通常用于持久化节点位置
onNodeClickonNodeClick={(event, node) => {}}点击节点时触发onNodeClick={(e, n) => setSelectedNode(n)}可用于选中节点或弹出编辑面板
onNodeDoubleClickonNodeDoubleClick={(event, node) => {}}双击节点时触发onNodeDoubleClick={(e, n) => openEditModal(n)}常用于打开编辑对话框
onDeleteonDelete={() => {}}当选中节点并按下 Delete 键时触发onDelete={() => deleteSelectedNodes()}需确保节点 deletable: true,且未被禁用

3.4 动态添加与更新节点

方法名称语法用途代码示例注意事项
setNodessetNodes((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 可监听删除事件
useReactFlowconst { setNodes, getNodes } = useReactFlow()在组件外获取操作方法const { setNodes } = useReactFlow(); setNodes([...])必须在 ReactFlow 子组件中使用
getNodesconst nodes = getNodes()获取当前所有节点const currentNodes = getNodes()返回最新节点数组,可用于计算或验证

第四章:边(Edges)详解

4.1 边的基本结构与属性

属性名称语法用途代码示例注意事项
idid: string边的唯一标识{ id: 'e1-2', source: '1', target: '2' }建议格式 e-${source}-${target}
sourcesource: string起始节点 idsource: '1'必须对应存在的节点
targettarget: string目标节点 idtarget: '2'必须对应存在的节点
typetype: string边的类型type: 'smoothstep'影响渲染样式,见 4.2 节
animatedanimated: boolean是否显示动画效果animated: true常用于表示激活或数据流动
labellabel: string | ReactNode显示在边上的标签label: '数据流'label: <div>标签</div>支持字符串或 JSX
stylestyle: CSSProperties自定义边样式style: { stroke: 'red', strokeWidth: 2 }可修改颜色、线宽等
classNameclassName: string添加 CSS 类名className: 'error-edge'用于配合 CSS 定制样式
markerStartmarkerStart: string起点标记(如箭头)markerStart: 'url(#arrow)'需预先定义 SVG marker
markerEndmarkerEnd: string终点标记markerEnd: 'url(#arrow)'常用 url(#arrow)url(#circle)
updatableupdatable: boolean | 'start' | 'end'是否可更新连接updatable: 'end'允许拖动边的起点或终点重新连接
focusablefocusable: boolean是否可被键盘聚焦focusable: true影响可访问性
deletabledeletable: boolean是否可通过 Delete 键删除deletable: false默认 true,设为 false 可防止误删

4.2 边的类型:默认边、贝塞尔边、步骤边、直线边、平滑步骤边

类型名称语法用途代码示例注意事项
defaulttype: 'default'默认贝塞尔曲线边type: 'default'平滑曲线,起点终点带切线方向
smoothsteptype: 'smoothstep'平滑转角的步骤边type: 'smoothstep'路径为折线但转角圆滑,适合水平/垂直布局
steptype: 'step'直角步骤边type: 'step'路径为严格直角折线
straighttype: 'straight'直线边type: 'straight'两点间直线连接
beziertype: 'bezier'贝塞尔曲线边(同 default)type: 'bezier'与 default 相同,保留别名

4.3 边的交互:连接、删除、标记(Markers)

方法/属性语法用途代码示例注意事项
onConnectonConnect={(params) => addEdge(params, edges)}处理连接事件import { addEdge } from 'reactflow';addEdge 工具函数自动生成边对象
onEdgeClickonEdgeClick={(event, edge) => {}}点击边时触发onEdgeClick={(e, ed) => setSelectedEdge(ed)}可用于选中边或弹出编辑菜单
onEdgeDoubleClickonEdgeDoubleClick={(event, edge) => {}}双击边时触发onEdgeDoubleClick={(e, ed) => openEdgeEditor(ed)}常用于编辑边标签或属性
onEdgesDeleteonEdgesDelete={(edges) => {}}当边被删除时触发onEdgesDelete={(eds) => removeFromState(eds)}接收被删除的边数组
markerEndmarkerEnd: 'url(#arrow)'设置终点箭头需配合 <Marker> 或内联定义常用箭头类型:‘arrow’, ‘arrowclosed’, ‘circle’
connectionLineTypeconnectionLineType="straight"设置连接预览线类型connectionLineType="smoothstep"影响拖拽连接时的预览线样式

4.4 自定义边组件

方法/属性语法用途代码示例注意事项
edgeTypesedgeTypes={{ '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可自定义位置和样式
getBezierPathgetBezierPath({ sourceX, sourceY, targetX, targetY })生成贝塞尔路径const [path, labelX, labelY] = getBezierPath(opts);用于自定义边中计算标准曲线路径
getStraightPathgetStraightPath(...)生成直线路径类似 getBezierPath,用于直线边参数结构相同
getSimpleBezierPathgetSimpleBezierPath(...)生成简化贝塞尔路径适用于简单连接

第五章:连接与交互控制

5.1 使用 Connectors 实现节点连接

概念/组件语法用途代码示例注意事项
Handle<Handle type="source" position={Position.Right} id="handle-1" />定义节点上的连接点(Connector)在自定义节点组件中使用,允许从该点连接或接收边type 必须为 ‘source’ 或 ‘target’;position 控制方向(Top/Right/Bottom/Left)
typetype: 'source' | 'target'指定连接点类型<Handle type="source" position={Position.Right} />source 表示可连线出发,target 表示可接收连接
positionposition: Position.Left/Right/Top/Bottom指定连接点所在边position={Position.Right}影响连接线的起始/终止方向
idid: string连接点唯一标识<Handle type="source" id="output" position={Position.Right} />当一个节点有多个连接点时必须设置 id,用于精确匹配 sourceHandle/targetHandle
isConnectableisConnectable: boolean | 0 | 1是否可连接<Handle type="source" isConnectable={false} />设为 false 后该点不可拖出连接线
stylestyle: CSSProperties自定义连接点样式<Handle style={{ background: 'red', width: 10 }} />可调整大小、颜色等
classNameclassName: string添加 CSS 类名<Handle className="custom-handle" />用于配合外部 CSS 文件定制外观

说明:Handle 是实现节点连接的核心组件,需嵌入在节点组件内部。用户通过拖拽 source 类型的 Handle 到另一个节点的 target 类型 Handle 上来创建边。

5.2 连接限制与连接验证(isValidConnection)

方法/属性语法用途代码示例注意事项
isValidConnectionisValidConnection={(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)

方法/属性语法用途代码示例注意事项
onConnectonConnect={(connection) => {}}连接成功时触发onConnect={(conn) => setEdges((eds) => addEdge(conn, eds))}connection 对象包含 source, target, sourceHandle, targetHandle
addEdgeaddEdge(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 启用/禁用拖拽与选择

方法/属性语法用途代码示例注意事项
nodesDraggablenodesDraggable={false}全局禁用所有节点拖拽<ReactFlow nodesDraggable={false} ... />优先级高于单个节点的 draggable 属性
nodesSelectablenodesSelectable={false}全局禁用节点选择<ReactFlow nodesSelectable={false} ... />点击节点不会被选中
elementsSelectableelementsSelectable={false}禁用所有元素(节点+边)选择<ReactFlow elementsSelectable={false} ... />包含节点和边的选择行为
edgesFocusableedgesFocusable={false}边是否可被聚焦<ReactFlow edgesFocusable={false} ... />影响键盘导航
panOnDragpanOnDrag={false}禁用拖拽画布平移<ReactFlow panOnDrag={false} ... />设为 false 后鼠标拖拽不会移动画布
panOnScrollpanOnScroll={false}禁用滚轮滚动平移<ReactFlow panOnScroll={false} ... />滚轮仅用于缩放(若启用)
panOnScrollModepanOnScrollMode="free" | "vertical" | "horizontal"设置滚轮平移模式panOnScrollMode="horizontal"panOnScroll={true} 时生效
zoomOnScrollzoomOnScroll={false}禁用滚轮缩放<ReactFlow zoomOnScroll={false} ... />默认为 true
zoomOnPinchzoomOnPinch={false}禁用双指缩放(触屏)<ReactFlow zoomOnPinch={false} ... />触屏设备适用
zoomOnDoubleClickzoomOnDoubleClick={false}禁用双击缩放<ReactFlow zoomOnDoubleClick={false} ... />双击画布不再触发缩放
preventScrollingpreventScrolling={false}允许画布外滚动<ReactFlow preventScrolling={false} ... />设为 false 后,当鼠标在画布上时,页面仍可滚动

使用场景

  • 查看模式:设置 nodesDraggable={false}nodesSelectable={false}panOnDrag={false} 禁止交互。
  • 专注编辑:禁用 zoomOnScroll 防止误操作。
  • 移动端优化:调整 panOnPinchpreventScrolling 提升体验。

第六章:事件系统与状态管理

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 管理全局状态

方法/属性语法用途代码示例注意事项
useReactFlowconst { setNodes, setEdges, getNodes, getEdges } = useReactFlow();获取状态操作方法在自定义组件中调用,无需通过 props 传递必须在 <ReactFlow> 子组件中使用
setNodessetNodes((nodes) => [...nodes, newNode])更新节点数组setNodes((nds) => nds.map(n => n.id === '1' ? {...n, selected: true} : n))推荐使用函数式更新
setEdgessetEdges((edges) => [...edges, newEdge])更新边数组setEdges((eds) => eds.filter(e => e.id !== 'e1'))同样支持函数式更新
setViewportsetViewport({ x, y, zoom })设置画布视口位置和缩放setViewport({ x: 0, y: 0, zoom: 1 })实现”居中”或”重置缩放”功能
getViewportconst viewport = getViewport()获取当前视口状态const { x, y, zoom } = getViewport()返回 { x, y, zoom } 对象
fitViewfitView({ padding, includeHiddenNodes })自动缩放以适配所有节点fitView({ padding: 0.1 })常用于初始化或重置视图
fitBoundsfitBounds({ x, y, width, height })缩放并平移以适配指定区域fitBounds({ x: 0, y: 0, width: 800, height: 600 })需要手动计算边界
projectproject({ x, y })将客户端坐标转换为画布坐标const pos = project({ x: e.clientX, y: e.clientY }); createNode(pos);用于在点击位置创建新节点

提示:useReactFlow 是管理 React Flow 全局状态的核心 Hook,避免了通过 props 层层传递状态更新函数。

6.3 获取当前元素(getNodes, getEdges)与更新状态

方法名称语法用途代码示例注意事项
getNodesconst nodes = getNodes()获取当前所有节点const currentNodes = getNodes(); const inputNodes = currentNodes.filter(n => n.type === 'input');返回最新节点数组,不受外部状态延迟影响
getEdgesconst 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
updateNodesetNodes(nodes.map(n => ...))更新特定节点属性setNodes(nodes => nodes.map(n => n.id === '1' ? {...n, data: {...n.data, label: 'New'}} : n))不可直接修改原对象,必须返回新数组
updateEdgesetEdges(edges.map(e => ...))更新特定边属性setEdges(edges => edges.map(e => e.id === 'e1-2' ? {...e, animated: true} : e))同样需返回新数组
addNodessetNodes(nodes => [...nodes, newNode])添加一个或多个节点setNodes(nodes => nodes.concat([{ id: '4', type: 'default', position: {x:100,y:100}, data: {label: 'Added'}}]))推荐使用 concat 或展开语法
removeNodessetNodes(nodes.filter(n => ...))删除节点setNodes(nodes => nodes.filter(n => !selectedIds.includes(n.id)))可结合 onNodesDelete 事件监听
removeEdgessetEdges(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 包裹事件处理器,避免不必要的重新渲染。
  • 结合 useStateuseReactFlow 实现复杂状态管理。
  • 利用 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
禁用平移panOnDragpanOnDrag={false}锁定画布不可拖拽移动<ReactFlow panOnDrag={false} />用于只读模式
禁用缩放zoomOnScroll, zoomOnPinchzoomOnScroll={false}禁用滚轮或手势缩放<ReactFlow zoomOnScroll={false} zoomOnPinch={false} />提升特定场景下的操作稳定性

提示:useReactFlow 提供的 setViewport 和 fitView 是控制视图的核心方法,常与按钮或快捷键结合使用。

7.2 使用 fitView 自动适配画布

参数类型默认值说明示例
paddingnumber0.1内容与画布边缘的留白比例fitView({ padding: 0.2 }) → 增加边距
includeHiddenNodesbooleanfalse是否包含隐藏节点在适配范围内fitView({ includeHiddenNodes: true })
nodesstring[]undefined指定仅适配某些节点(通过 id)fitView({ nodes: ['node-1', 'node-2'] })
durationnumberundefined动画持续时间(毫秒)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, zoomOnPinchzoomOnScroll={false}
双击缩放(默认双击放大)zoomOnDoubleClickzoomOnDoubleClick={false}
空格键 + 拖拽临时启用画布拖拽(即使 panOnDrag=false)无法禁用可通过 CSS 或事件拦截覆盖
Delete / Backspace删除选中节点或边deleteKeyCodedeleteKeyCode={null}deleteKeyCode="Delete"
Ctrl + A全选所有节点(若 selectNodesOnDrag 启用)无直接禁用需拦截键盘事件
Ctrl + Z撤销(需自行实现)需结合状态管理实现
F居中并适配所有节点(等效 fitView)fitViewOnClickfitViewOnClick={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适合复杂网络图
elkjsEclipse 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" />
positionPosition.Left/Right/Top/BottomPosition.Right连接点方向position={Position.Bottom}
idstringundefined唯一标识(多连接点时必需)<Handle id="a" type="source" />
isConnectableboolean | 0 | 1true是否可连接isConnectable={false}
styleCSSProperties{}自定义样式style={{ background: 'red', width: 10 }}
classNamestring自定义类名className="custom-handle"
onConnect({ connection }) => voidundefined连接时回调onConnect={(params) => console.log(params)}
isValidConnection(connection) => booleanundefined自定义连接验证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; }>;
NodeTypeReact.ComponentType<NodeProps<T>>自定义节点组件类型const CustomNode: NodeType<{ label: string }> = ({ data }) => {...};
EdgeTypeReact.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 和 typeconst nodes: Node<{ name: string }, 'user'>[] = [...];
自定义 Handle 类型扩展 HandleProps类型安全的 Handleinterface MyHandleProps extends HandleProps { customProp?: boolean; }

提示:使用 TypeScript 可显著提升开发体验,提供自动补全和编译时检查,减少运行时错误。建议在大型项目中启用。

第九章:性能优化与最佳实践

9.1 大规模节点渲染优化(React.memo、useCallback)

优化策略实现方式用途代码示例注意事项
节点组件 memo 化React.memo(CustomNode)防止节点不必要的重渲染const MemoizedNode = React.memo(CustomNode);仅当 props 变化时重新渲染
使用 useCallbackuseCallback(() => {...}, [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.memoconst 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.logonError={(err) => console.error(err)}监听 React Flow 内部错误
React DevTools检查组件树查看节点组件渲染情况分析重渲染问题
性能分析React ProfilerProfiler 组件包装定位性能瓶颈
节点/边数据验证运行时校验if (!node.id) throw new Error(...)确保数据结构正确
边界情况处理空状态、null 节点nodes?.map(...) 或默认值提升健壮性
自定义 Hook 调试useDebugValue在自定义 Hook 中显示状态便于调试状态逻辑

第十章:集成与扩展

10.1 保存与恢复流程图状态(序列化)

操作方法示例说明
获取当前状态getNodes(), getEdges()const flow = { nodes: getNodes(), edges: getEdges() };获取最新数据
序列化为 JSONJSON.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, setEdgesuseEffect(() => { 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轻量,无需 providerconst useFlowStore = create(...) => ({ nodes, edges, setNodes })
Context API自定义 Context原生方案,适合中小型应用FlowContext.Provider value={state}
与 useReactFlow 协同从 store 获取数据保持状态同步const { nodes } = useFlowStore();

10.3 与后端 API 通信

场景实现方式方法注意事项
加载初始流程useEffect + fetchGET /api/flows/:id错误处理和加载状态
保存流程onSave + fetchPUT /api/flows/:id支持自动保存(定时或 onMoveEnd)
实时协作WebSocketsocket.on('flow-update', updateFlow)需处理冲突合并
验证连接API 校验POST /api/validate-connection在 onConnect 中调用
执行流程触发后端执行POST /api/flows/:id/run返回执行结果或日志
权限控制请求头携带 tokenheaders: { 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 测试确保加载和交互性能