React Flow 作为基于 React 的现代化工作流编排工具,通过其声明式渲染协议和可扩展的节点系统,已成为百宝箱、扣子等主流工作流平台的核心画布引擎, 服务于智能体的搭建。
一、React Flow 简介
React Flow 是一个用于构建基于节点的应用程序的库。官方文档 reactflow.dev/learn 可以实现自定义节点类型和边,它还视口控件等等组件。


- 易于使用:已经具备了许多开箱即用的功能。拖动节点、缩放和平移、选择多个节点和边缘以及添加/删除边都是内置功能。
- 可定制:支持自定义节点类型和边类型。由于自定义节点只是 React 组件,因此你可以实现任何需要的功能,不会被锁定在内置的节点类型中。 自定义边可让你在节点边添加标签、控件和定制逻辑。
- 快速渲染:只渲染发生变化的节点,并确保只显示视口中的节点。
- 内置插件 :提供了一些开箱即用的插件,如
<Background />
可实现一些基本的自定义背景图案;<MiniMap />
可在屏幕一角显示小版本的图形;<Controls />
可添加缩放、居中和锁定视口的控件;<Panel />
可让你轻松将内容放置在视口的顶部;<NodeToolbar />
可让你呈现连接到节点的工具栏;<NodeResizer />
可让你轻松为节点添加调整大小的功能。
二、使用步骤
(一)安装
sql
yarn add @xyflow/react
npm install @xyflow/react
(二)创建第一个 Flow
reactflow
软件包导出 <ReactFlow />
组件作为默认导出。加上一些节点和边,就可以开始工作了。
javascript
import React from 'react';
import { ReactFlow } from '@xyflow/react';
import '@xyflow/react/dist/style.css';
// 初始化节点
// 节点唯一 ID
const initialNodes = [
{ id: '1', position: { x: 0, y: 0 }, data: { label: '1' } },
{ id: '2', position: { x: 0, y: 100 }, data: { label: '2' } },
];
// 初始化边
// 边存在唯一 ID
const initialEdges = [{ id: 'e1-2', source: '1', target: '2' }];
export default function App() {
return (
<div style={{ width: '100vw', height: '100vh' }}>
<ReactFlow nodes={initialNodes} edges={initialEdges} />
</div>
);
}
(三)节点 & 边介绍
1.1 自定义节点
javascript
function TextUpdaterNode(props) {
const onChange = useCallback((evt) => {
console.log(evt.target.value);
}, []);
return (
<div className="text-updater-node">
<div>
<label htmlFor="text">Text:</label>
<input id="text" name="text" onChange={onChange} className="nodrag" />
</div>
</div>
);
}
1.2 注册节点
ini
const nodeTypes = {
textUpdater: TextUpdaterNode
};
function Flow() {
...
return (
<ReactFlow
nodes={nodes}
edges={edges}
nodeTypes={nodeTypes}
...
/>
);
}
1.3 消费自定义节点
yaml
const nodes = [
{
id: 'node-1',
type: 'textUpdater',
position: { x: 0, y: 0 },
data: { value: 123 },
},
];
批量设置节点 setNodes
javascript
export function useNodesState<NodeType extends Node>(
initialNodes: NodeType[]
): [
nodes: NodeType[],
setNodes: Dispatch<SetStateAction<NodeType[]>>,
onNodesChange: OnNodesChange<NodeType>
] {
const [nodes, setNodes] = useState(initialNodes);
const onNodesChange: OnNodesChange<NodeType> = useCallback(
(changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
[]
);
return [nodes, setNodes, onNodesChange];
}
1.4 自定义边
javascript
import React from 'react';
import ReactFlow, { BaseEdge, EdgeLabelRenderer } from 'reactflow';
// 1. 定义自定义边组件
const CustomEdge = ({
id,
sourceX,
sourceY,
targetX,
targetY,
sourcePosition,
targetPosition,
style = {},
markerEnd,
}) => {
const [edgePath, labelX, labelY] = getCustomEdge({
sourceX,
sourceY,
sourcePosition,
targetX,
targetY,
targetPosition,
});
return (
<>
<BaseEdge path={edgePath} markerEnd={markerEnd} style={style} />
<EdgeLabelRenderer>
<div
style={{
position: 'absolute',
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
background: '#ffcc00',
padding: 5,
borderRadius: 5,
fontSize: 12,
fontWeight: 700,
}}
className="nodrag nopan"
>
自定义边
</div>
</EdgeLabelRenderer>
</>
);
};
1.5 边注册
ini
// 2. 定义边类型
const edgeTypes = {
custom: CustomEdge,
};
// 3. 在ReactFlow中使用
function Flow() {
return (
<div style={{ width: '100%', height: '500px' }}>
<ReactFlow
edgeTypes={edgeTypes}
nodes={[...]}
edges={[...]}
/>
</div>
);
}
批量设置边 setEdges
ini
export function useEdgesState<EdgeType extends Edge = Edge>(
initialEdges: EdgeType[]
): [
//
edges: EdgeType[],
setEdges: Dispatch<SetStateAction<EdgeType[]>>,
onEdgesChange: OnEdgesChange<EdgeType>
] {
const [edges, setEdges] = useState(initialEdges);
const onEdgesChange: OnEdgesChange<EdgeType> = useCallback(
(changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),
[]
);
return [edges, setEdges, onEdgesChange];
}
(四)边事件详解
React Flow 提供了多种边(Edge)相关的事件回调,用于处理用户与边的交互行为。以下是核心边事件及其用法:
1.1 onEdgeClick
-
触发时机:用户单击边时触发。
-
参数 :
event
: 鼠标事件对象edge
: 被点击的边对象(包含id
、source
、target
等属性)
-
典型用途 :
- 显示边的详细信息(如弹窗)
- 选中边并高亮关联节点
jsxconst onEdgeClick = (event, edge) => { console.log('Clicked edge:', edge); // 示例:显示边属性弹窗 setSelectedEdge(edge); };
1.2 onEdgeDoubleClick
-
触发时机:用户双击边时触发。
-
参数 :同
onEdgeClick
-
典型用途 :
- 快速编辑边属性
- 删除边(需配合确认逻辑)
jsxconst onEdgeDoubleClick = (event, edge) => { if (confirm('确认删除此边?')) { setEdges(eds => eds.filter(e => e.id !== edge.id)); } };
1.3 onEdgeMouseEnter
/ onEdgeMouseLeave
-
触发时机:鼠标悬停/离开边时触发。
-
参数 :同
onEdgeClick
-
典型用途 :
- 动态修改边样式(如颜色、虚线)
- 显示悬停提示(Tooltip)
jsxconst [hoveredEdgeId, setHoveredEdgeId] = useState(null); const onEdgeMouseEnter = (event, edge) => setHoveredEdgeId(edge.id); const onEdgeMouseLeave = () => setHoveredEdgeId(null); // 动态样式 const edgesWithStyle = edges.map(edge => ({ ...edge, style: hoveredEdgeId === edge.id ? { stroke: 'red' } : {} }));
1.4 onEdgesDelete
-
触发时机:边被删除时触发(通过键盘 Delete 键或编程删除)。
-
参数 :被删除的边数组
Edge[]
-
典型用途 :
- 清理与边关联的数据
- 记录删除操作日志
jsxconst onEdgesDelete = (deletedEdges) => { console.log('Deleted edges:', deletedEdges); // 同步更新后端数据 };
1.5 onEdgesChange
-
触发时机:边状态变化时触发(如选中、取消选中)。
-
参数 :边变更数组
EdgeChange[]
-
典型用途 :
- 受控模式下同步边状态
- 与
useEdgesState
钩子配合使用
jsxconst [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); <ReactFlow onEdgesChange={onEdgesChange} />
2.1 onConnect
-
触发时机:用户成功连接两个节点时触发。
-
参数 :连接对象
Connection
(包含source
、target
、sourceHandle
等) -
典型用途 :
- 将连接转换为边(需配合
addEdge
工具) - 验证连接合法性(如禁止循环连接)
jsxconst onConnect = (connection) => { const newEdge = { ...connection, id: `edge-${Date.now()}` }; setEdges(eds => addEdge(newEdge, eds)); }; // setEdges 边设置方法
- 将连接转换为边(需配合
2.2 onReconnect
-
触发时机:用户拖动边的端点重新连接到其他节点时触发。
-
参数 :
oldEdge
: 原边对象newConnection
: 新连接对象
-
典型用途 :
- 更新边数据源/目标
- 检查连接有效性(如端口类型匹配)
jsxconst onReconnect = (oldEdge, newConnection) => { setEdges(eds => eds.map(edge => edge.id === oldEdge.id ? { ...edge, ...newConnection } : edge )); };
3.1 isValidConnection
-
触发时机:用户尝试建立连接时实时验证。
-
参数 :连接对象
Connection
-
返回 :
boolean
(true
表示允许连接) -
典型用途 :
- 禁止自连接
- 限制特定类型节点的连接
jsxconst isValidConnection = (connection) => { return connection.source !== connection.target; // 禁止自连接 };
3.2 onEdgeUpdateStart
/ onEdgeUpdateEnd
-
触发时机:开始/结束拖动边端点时触发。
-
参数 :
event
: 鼠标事件edge
: 被拖动的边handleType
: "source" 或 "target"
-
典型用途 :
- 显示拖动提示
- 记录操作状态
jsxconst onEdgeUpdateStart = (event, edge, handleType) => { console.log(`开始拖动边 ${edge.id} 的 ${handleType} 端点`); };
完整示例代码
jsx
import { ReactFlow, useEdgesState, addEdge } from 'reactflow';
function FlowComponent() {
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const onConnect = (connection) =>
setEdges(eds => addEdge({ ...connection, animated: true }, eds));
const onEdgeClick = (event, edge) => alert(`点击边: ${edge.id}`);
return (
<ReactFlow
edges={edges}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onEdgeClick={onEdgeClick}
isValidConnection={(conn) => conn.source !== conn.target}
/>
);
}
总结
- 交互控制 :通过
onEdgeClick
、onEdgeMouseEnter
等实现边的高亮与提示。 - 数据管理 :
onEdgesChange
和useEdgesState
配合管理边状态。 - 连接验证 :
isValidConnection
确保连接符合业务规则。 - 高级功能 :
onReconnect
支持动态调整边端点。
更多细节可参考 React Flow 官方文档。
(五) 节点事件详解
核心节点事件概览
ReactFlow 提供了一系列节点相关的事件处理程序,允许开发者对用户的交互行为做出响应。以下是主要的节点事件类型及其触发时机:
- onNodeClick - 当用户点击节点时触发
- onNodeDoubleClick - 当用户双击节点时触发
- onNodeDragStart - 当用户开始拖动节点时触发
- onNodeDrag - 当用户拖动节点过程中持续触发
- onNodeDragStop - 当用户停止拖动节点时触发
- onNodeMouseEnter - 当鼠标进入节点区域时触发
- onNodeMouseMove - 当鼠标在节点区域内移动时触发
- onNodeMouseLeave - 当鼠标离开节点区域时触发
- onNodeContextMenu - 当用户在节点上右键点击时触发
- onNodesDelete - 当节点被删除时触发
1.1 点击相关事件
onNodeClick 是最常用的节点事件之一,它允许开发者在用户点击节点时执行自定义逻辑。这个事件接收两个参数:事件对象和被点击的节点对象。
jsx
const onNodeClick = useCallback((event, node) => {
console.log('Node clicked:', node.id, node.data);
// 可以在这里更新节点状态或执行其他操作
}, []);
onNodeDoubleClick 类似于单击事件,但只在快速连续点击两次时触发。这在需要区分单击和双击操作的场景中非常有用。
1.2 拖拽相关事件
ReactFlow 提供了完整的拖拽生命周期事件,使开发者能够精确控制节点的拖拽行为:
- onNodeDragStart:拖拽开始时触发,适合用于初始化拖拽状态或记录原始位置
- onNodeDrag:拖拽过程中持续触发,可用于实时更新相关UI或执行碰撞检测
- onNodeDragStop:拖拽结束时触发,适合用于提交最终位置或执行验证
jsx
const onNodeDragStart = useCallback((event, node) => {
console.log('Drag started for node:', node.id);
}, []);
const onNodeDrag = useCallback((event, node) => {
// 实时更新节点位置或其他相关状态
}, []);
const onNodeDragStop = useCallback((event, node) => {
console.log('Drag ended for node:', node.id, 'at position:', node.position);
}, []);
1.3 鼠标悬停事件
鼠标悬停相关事件对于创建响应式UI非常有用:
- onNodeMouseEnter:鼠标进入节点区域时触发,适合用于显示工具提示或高亮节点
- onNodeMouseMove:鼠标在节点内移动时触发,可用于实现精细的鼠标跟踪效果
- onNodeMouseLeave:鼠标离开节点区域时触发,适合用于清除悬停状态
jsx
const [hoveredNode, setHoveredNode] = useState(null);
const onNodeMouseEnter = useCallback((event, node) => {
setHoveredNode(node.id);
}, []);
const onNodeMouseLeave = useCallback(() => {
setHoveredNode(null);
}, []);
需要注意的是,在某些情况下,自定义节点可能会出现鼠标事件"闪烁"的问题,即鼠标在节点边缘时快速交替触发enter和leave事件。这通常是由于节点边缘检测区域的问题导致的。
1.4 上下文菜单事件
onNodeContextMenu 允许开发者在节点上实现自定义右键菜单功能。默认情况下,浏览器会显示原生上下文菜单,因此通常需要调用 event.preventDefault()
来阻止默认行为。浏览器菜单事件参考见菜单事件
jsx
const onNodeContextMenu = useCallback((event, node) => {
event.preventDefault();
console.log('Context menu for node:', node.id);
// 显示自定义上下文菜单
}, []);
(六) 一些使用说明
1.1 性能优化
由于 ReactFlow 的事件处理程序会在每次交互时触发,使用 useCallback
来包装处理函数可以避免不必要的重新渲染。
jsx
const onNodeClick = useCallback((event, node) => {
// 处理逻辑
}, [dependencies]);
Reactflow 中一些方法例如

会有重渲染的问题, 如果需要获取当前工作流画布的缩放比例, 可以考虑一些替代性方法封装成 hooks 使用。如下给出替代方案
ini
// 只订阅 zoom 变化
const zoom = useStore(store => store.transform[2]);
1.2 结合 useNodesState 管理节点状态
对于需要根据事件更新节点状态的场景,可以使用 useNodesState
钩子来简化状态管理。
jsx
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const onNodeClick = useCallback((event, node) => {
setNodes(nds => nds.map(n => {
if (n.id === node.id) {
return {
...n,
data: { ...n.data, clicked: true }
};
}
return n;
}));
}, [setNodes]);
React Flow 作为基于 React 的现代化工作流编排工具,通过其声明式渲染协议和可扩展的节点系统,已成为百宝箱、扣子等主流工作流平台的核心画布引擎, 服务于智能体的搭建。 后续会从渲染协议继续介绍到一般性消费协议,聊一聊如何把画布渲染的数据转化成服务能够消费的数据,供大模型或者其他插件等等技能消费,以最终实现智能体的编排输出。