名称:reactflow-fundamentals 用户可调用:false 描述:使用 React Flow 构建基于节点的用户界面、流程图、工作流编辑器或交互图表时使用。涵盖设置、节点、边、控件和交互性。 允许工具:
- Bash
- Read
React Flow 基础
使用 React Flow 构建可自定义的节点编辑器和交互图表。 本技能涵盖核心概念、设置和创建流程界面的常见模式。
安装
# npm
npm install @xyflow/react
# pnpm
pnpm add @xyflow/react
# yarn
yarn add @xyflow/react
基本设置
import { useCallback } from 'react';
import {
ReactFlow,
useNodesState,
useEdgesState,
addEdge,
Background,
Controls,
MiniMap,
type Node,
type Edge,
type OnConnect,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
const initialNodes: Node[] = [
{
id: '1',
type: 'input',
data: { label: '开始' },
position: { x: 250, y: 0 },
},
{
id: '2',
data: { label: '处理' },
position: { x: 250, y: 100 },
},
{
id: '3',
type: 'output',
data: { label: '结束' },
position: { x: 250, y: 200 },
},
];
const initialEdges: Edge[] = [
{ id: 'e1-2', source: '1', target: '2' },
{ id: 'e2-3', source: '2', target: '3' },
];
export default function Flow() {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onConnect: OnConnect = useCallback(
(params) => setEdges((eds) => addEdge(params, eds)),
[setEdges]
);
return (
<div style={{ width: '100vw', height: '100vh' }}>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
fitView
>
<Background />
<Controls />
<MiniMap />
</ReactFlow>
</div>
);
}
节点类型
内置节点类型
const nodes: Node[] = [
// 输入节点 - 仅具有源句柄
{
id: '1',
type: 'input',
data: { label: '输入节点' },
position: { x: 0, y: 0 },
},
// 默认节点 - 具有源和目标句柄
{
id: '2',
type: 'default',
data: { label: '默认节点' },
position: { x: 0, y: 100 },
},
// 输出节点 - 仅具有目标句柄
{
id: '3',
type: 'output',
data: { label: '输出节点' },
position: { x: 0, y: 200 },
},
];
节点配置
const node: Node = {
id: '唯一-id',
type: 'default',
position: { x: 100, y: 100 },
data: { label: '我的节点', customProp: '值' },
// 可选属性
style: { backgroundColor: '#f0f0f0' },
className: '自定义节点',
sourcePosition: Position.Right,
targetPosition: Position.Left,
draggable: true,
selectable: true,
connectable: true,
deletable: true,
hidden: false,
selected: false,
dragging: false,
zIndex: 0,
extent: 'parent', // 约束到父节点
parentId: '父节点-id', // 用于嵌套节点
expandParent: true, // 当节点超出边界时扩展父节点
};
边类型
内置边类型
import { MarkerType } from '@xyflow/react';
const edges: Edge[] = [
// 默认边(贝塞尔曲线)
{
id: 'e1',
source: '1',
target: '2',
type: 'default',
},
// 直线
{
id: 'e2',
source: '2',
target: '3',
type: 'straight',
},
// 阶梯边(直角)
{
id: 'e3',
source: '3',
target: '4',
type: 'step',
},
// 平滑阶梯边(圆角)
{
id: 'e4',
source: '4',
target: '5',
type: 'smoothstep',
},
];
边配置
const edge: Edge = {
id: '边-id',
source: '源节点-id',
target: '目标节点-id',
// 可选属性
type: 'smoothstep',
sourceHandle: '句柄-a',
targetHandle: '句柄-b',
label: '边标签',
labelStyle: { fill: '#333', fontWeight: 700 },
labelBgStyle: { fill: '#fff' },
labelBgPadding: [8, 4],
labelBgBorderRadius: 4,
style: { stroke: '#333', strokeWidth: 2 },
animated: true,
markerEnd: {
type: MarkerType.ArrowClosed,
color: '#333',
},
markerStart: {
type: MarkerType.Arrow,
},
interactionWidth: 20,
deletable: true,
selectable: true,
selected: false,
hidden: false,
zIndex: 0,
data: { customProp: '值' },
};
句柄
import { Handle, Position, type NodeProps } from '@xyflow/react';
function CustomNode({ data }: NodeProps) {
return (
<div className="自定义节点">
{/* 目标句柄(输入) */}
<Handle
type="target"
position={Position.Top}
id="输入"
style={{ background: '#555' }}
isConnectable={true}
/>
<div>{data.label}</div>
{/* 多个源句柄 */}
<Handle
type="source"
position={Position.Bottom}
id="输出-a"
style={{ left: '25%', background: '#555' }}
/>
<Handle
type="source"
position={Position.Bottom}
id="输出-b"
style={{ left: '75%', background: '#555' }}
/>
</div>
);
}
插件组件
背景
import { Background, BackgroundVariant } from '@xyflow/react';
<ReactFlow nodes={nodes} edges={edges}>
{/* 点图案 */}
<Background variant={BackgroundVariant.Dots} gap={12} size={1} />
{/* 线图案 */}
<Background variant={BackgroundVariant.Lines} gap={20} />
{/* 交叉图案 */}
<Background variant={BackgroundVariant.Cross} gap={25} />
{/* 自定义样式 */}
<Background
color="#aaa"
gap={16}
size={1}
variant={BackgroundVariant.Dots}
/>
</ReactFlow>
控件
import { Controls } from '@xyflow/react';
<ReactFlow nodes={nodes} edges={edges}>
<Controls
showZoom={true}
showFitView={true}
showInteractive={true}
position="bottom-left"
/>
</ReactFlow>
迷你地图
import { MiniMap } from '@xyflow/react';
<ReactFlow nodes={nodes} edges={edges}>
<MiniMap
nodeColor={(node) => {
switch (node.type) {
case 'input':
return '#0041d0';
case 'output':
return '#ff0072';
default:
return '#1a192b';
}
}}
nodeStrokeWidth={3}
zoomable
pannable
/>
</ReactFlow>
面板
import { Panel } from '@xyflow/react';
<ReactFlow nodes={nodes} edges={edges}>
<Panel position="top-left">
<button onClick={onSave}>保存</button>
<button onClick={onRestore}>恢复</button>
</Panel>
<Panel position="top-right">
<div>节点数量:{nodes.length}</div>
</Panel>
</ReactFlow>
事件处理
import {
ReactFlow,
type NodeMouseHandler,
type EdgeMouseHandler,
type OnSelectionChangeFunc,
} from '@xyflow/react';
function Flow() {
// 节点事件
const onNodeClick: NodeMouseHandler = useCallback((event, node) => {
console.log('节点点击:', node.id);
}, []);
const onNodeDoubleClick: NodeMouseHandler = useCallback((event, node) => {
console.log('节点双击:', node.id);
}, []);
const onNodeDragStart: NodeMouseHandler = useCallback((event, node) => {
console.log('拖动开始:', node.id);
}, []);
const onNodeDrag: NodeMouseHandler = useCallback((event, node) => {
console.log('拖动中:', node.position);
}, []);
const onNodeDragStop: NodeMouseHandler = useCallback((event, node) => {
console.log('拖动停止:', node.position);
}, []);
// 边事件
const onEdgeClick: EdgeMouseHandler = useCallback((event, edge) => {
console.log('边点击:', edge.id);
}, []);
// 选择变化
const onSelectionChange: OnSelectionChangeFunc = useCallback(
({ nodes, edges }) => {
console.log('选中的节点:', nodes);
console.log('选中的边:', edges);
},
[]
);
return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodeClick={onNodeClick}
onNodeDoubleClick={onNodeDoubleClick}
onNodeDragStart={onNodeDragStart}
onNodeDrag={onNodeDrag}
onNodeDragStop={onNodeDragStop}
onEdgeClick={onEdgeClick}
onSelectionChange={onSelectionChange}
/>
);
}
视口控制
import { useReactFlow } from '@xyflow/react';
function ViewportControls() {
const { zoomIn, zoomOut, fitView, setCenter, setViewport, getViewport } =
useReactFlow();
return (
<div>
<button onClick={() => zoomIn()}>放大</button>
<button onClick={() => zoomOut()}>缩小</button>
<button onClick={() => fitView({ padding: 0.2 })}>适应视图</button>
<button onClick={() => setCenter(0, 0, { zoom: 1 })}>居中</button>
<button
onClick={() => {
const viewport = getViewport();
console.log('当前视口:', viewport);
}}
>
日志视口
</button>
</div>
);
}
// 必须在 ReactFlowProvider 内部使用
function App() {
return (
<ReactFlowProvider>
<Flow />
<ViewportControls />
</ReactFlowProvider>
);
}
使用 useReactFlow 的节点操作
import { useReactFlow, type Node } from '@xyflow/react';
function NodeOperations() {
const { getNodes, setNodes, getNode, addNodes, deleteElements } =
useReactFlow();
const addNewNode = () => {
const newNode: Node = {
id: `node-${Date.now()}`,
data: { label: '新节点' },
position: { x: Math.random() * 300, y: Math.random() * 300 },
};
addNodes(newNode);
};
const updateNode = (id: string, data: object) => {
setNodes((nodes) =>
nodes.map((node) =>
node.id === id ? { ...node, data: { ...node.data, ...data } } : node
)
);
};
const deleteNode = (id: string) => {
deleteElements({ nodes: [{ id }] });
};
const getAllNodes = () => {
const nodes = getNodes();
console.log('所有节点:', nodes);
};
return (
<div>
<button onClick={addNewNode}>添加节点</button>
<button onClick={getAllNodes}>日志节点</button>
</div>
);
}
保存和恢复状态
import { useReactFlow, type ReactFlowJsonObject } from '@xyflow/react';
function SaveRestore() {
const { toObject, setNodes, setEdges, setViewport } = useReactFlow();
const onSave = useCallback(() => {
const flow = toObject();
localStorage.setItem('flow', JSON.stringify(flow));
}, [toObject]);
const onRestore = useCallback(() => {
const restoreFlow = async () => {
const flow = JSON.parse(
localStorage.getItem('flow') || '{}'
) as ReactFlowJsonObject;
if (flow.nodes && flow.edges) {
setNodes(flow.nodes);
setEdges(flow.edges);
if (flow.viewport) {
setViewport(flow.viewport);
}
}
};
restoreFlow();
}, [setNodes, setEdges, setViewport]);
return (
<Panel position="top-right">
<button onClick={onSave}>保存</button>
<button onClick={onRestore}>恢复</button>
</Panel>
);
}
何时使用此技能
使用 reactflow-fundamentals 当您需要:
- 构建工作流构建器或无代码编辑器
- 创建数据管道可视化
- 设计状态机图
- 构建聊天机器人对话流
- 创建组织结构图
- 设计电路图
- 构建机器学习管道可视化器
- 创建交互式决策树
最佳实践
- 为节点和边使用唯一 ID
- 使用 useCallback 记忆回调
- 使用 TypeScript 以确保类型安全
- 保持节点组件纯净且高效
- 使用 CSS 类而非内联样式进行复杂样式
- 将流状态存储在中央状态管理器以处理复杂应用
- 在初始渲染时使用 fitView() 以提升用户体验
- 为常见操作添加键盘快捷键
- 实现撤销/重做以改善用户体验
- 在连接前进行节点验证