react-flow-implementation by existential-birds/beagle
npx skills add https://github.com/existential-birds/beagle --skill react-flow-implementationimport { ReactFlow, useNodesState, useEdgesState, addEdge } from '@xyflow/react';
import '@xyflow/react/dist/style.css';
const initialNodes = [
{ id: '1', position: { x: 0, y: 0 }, data: { label: 'Node 1' } },
{ id: '2', position: { x: 200, y: 100 }, data: { label: 'Node 2' } },
];
const initialEdges = [{ id: 'e1-2', source: '1', target: '2' }];
export default function Flow() {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onConnect = useCallback(
(connection) => setEdges((eds) => addEdge(connection, eds)),
[setEdges]
);
return (
<div style={{ width: '100%', height: '100vh' }}>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
fitView
/>
</div>
);
}
import type { Node, Edge, NodeProps, BuiltInNode } from '@xyflow/react';
// 定义带有数据形状的自定义节点类型
type CustomNode = Node<{ value: number; label: string }, 'custom'>;
// 与内置节点组合
type MyNode = CustomNode | BuiltInNode;
type MyEdge = Edge<{ weight?: number }>;
// 在整个应用中使用
const [nodes, setNodes] = useNodesState<MyNode>(initialNodes);
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
import { memo } from 'react';
import { Handle, Position, type NodeProps } from '@xyflow/react';
// 定义节点类型
type CounterNode = Node<{ count: number }, 'counter'>;
// 始终用 memo 包装以提高性能
const CounterNode = memo(function CounterNode({ data, isConnectable }: NodeProps<CounterNode>) {
return (
<>
<Handle type="target" position={Position.Top} isConnectable={isConnectable} />
<div className="counter-node">
Count: {data.count}
{/* nodrag 防止与按钮交互时触发拖动 */}
<button className="nodrag" onClick={() => console.log('clicked')}>
Increment
</button>
</div>
<Handle type="source" position={Position.Bottom} isConnectable={isConnectable} />
</>
);
});
// 在 nodeTypes 中注册(在组件外部定义以避免重新渲染)
const nodeTypes = { counter: CounterNode };
// 在 ReactFlow 中使用
<ReactFlow nodeTypes={nodeTypes} ... />
// 当节点有多个相同类型的连接点时,使用 handle ID
<Handle type="source" position={Position.Right} id="a" />
<Handle type="source" position={Position.Right} id="b" style={{ top: 20 }} />
// 连接到特定的连接点
const edge = {
id: 'e1-2',
source: '1',
sourceHandle: 'a',
target: '2',
targetHandle: null
};
import { BaseEdge, EdgeProps, getSmoothStepPath } from '@xyflow/react';
function CustomEdge({ id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, data }: EdgeProps) {
const [edgePath, labelX, labelY] = getSmoothStepPath({
sourceX, sourceY, sourcePosition,
targetX, targetY, targetPosition,
});
return (
<>
<BaseEdge id={id} path={edgePath} />
<text x={labelX} y={labelY} className="edge-label">{data?.label}</text>
</>
);
}
const edgeTypes = { custom: CustomEdge };
// 带有变更处理器的外部状态
const [nodes, setNodes] = useState<Node[]>(initialNodes);
const [edges, setEdges] = useState<Edge[]>(initialEdges);
const onNodesChange = useCallback(
(changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
[]
);
const onEdgesChange = useCallback(
(changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),
[]
);
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
/>
import { useReactFlow, ReactFlowProvider } from '@xyflow/react';
function FlowControls() {
const {
getNodes, setNodes, addNodes, updateNodeData,
getEdges, setEdges, addEdges,
fitView, zoomIn, zoomOut, setViewport,
deleteElements, toObject,
} = useReactFlow();
const addNode = () => {
addNodes({ id: `${Date.now()}`, position: { x: 100, y: 100 }, data: { label: 'New' } });
};
return <button onClick={addNode}>添加节点</button>;
}
// 使用 useReactFlow 时必须包装在 provider 中
function App() {
return (
<ReactFlowProvider>
<Flow />
<FlowControls />
</ReactFlowProvider>
);
}
const { updateNodeData } = useReactFlow();
// 与现有数据合并
updateNodeData(nodeId, { label: 'Updated' });
// 完全替换数据
updateNodeData(nodeId, { newField: 'value' }, { replace: true });
// 初始渲染时适应视图
<ReactFlow fitView fitViewOptions={{ padding: 0.2, maxZoom: 1 }} />
// 程序化控制
const { fitView, setViewport, getViewport, zoomTo } = useReactFlow();
// 适应特定节点
fitView({ nodes: [{ id: '1' }, { id: '2' }], duration: 500 });
// 设置精确视口
setViewport({ x: 100, y: 100, zoom: 1.5 }, { duration: 300 });
const isValidConnection = useCallback((connection: Connection) => {
// 防止自连接
if (connection.source === connection.target) return false;
// 自定义验证逻辑
const sourceNode = getNode(connection.source);
const targetNode = getNode(connection.target);
return sourceNode?.type !== targetNode?.type;
}, []);
<ReactFlow isValidConnection={isValidConnection} />
<ReactFlow
// 核心数据
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
// 自定义类型(在组件外部定义)
nodeTypes={nodeTypes}
edgeTypes={edgeTypes}
// 连接
onConnect={onConnect}
connectionMode={ConnectionMode.Loose} // 允许目标到目标连接
isValidConnection={isValidConnection}
// 视口
fitView
minZoom={0.1}
maxZoom={4}
defaultViewport={{ x: 0, y: 0, zoom: 1 }}
// 交互
nodesDraggable={true}
nodesConnectable={true}
elementsSelectable={true}
panOnDrag={true}
zoomOnScroll={true}
// 附加组件
<MiniMap />
<Controls />
<Background variant={BackgroundVariant.Dots} />
</ReactFlow>
| 类名 | 效果 |
|---|---|
nodrag | 点击元素时防止拖动 |
nowheel | 防止滚轮缩放事件 |
nopan | 防止从元素平移 |
nokey | 防止键盘事件(用于输入框) |
有关 MiniMap、Controls、Background、NodeToolbar、NodeResizer 的详细信息,请参阅 ADDITIONAL_COMPONENTS.md。
每周安装量
148
代码仓库
GitHub 星标数
41
首次出现
2026年1月20日
安全审计
已安装于
opencode126
gemini-cli123
codex122
github-copilot112
cursor106
claude-code96
import { ReactFlow, useNodesState, useEdgesState, addEdge } from '@xyflow/react';
import '@xyflow/react/dist/style.css';
const initialNodes = [
{ id: '1', position: { x: 0, y: 0 }, data: { label: 'Node 1' } },
{ id: '2', position: { x: 200, y: 100 }, data: { label: 'Node 2' } },
];
const initialEdges = [{ id: 'e1-2', source: '1', target: '2' }];
export default function Flow() {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onConnect = useCallback(
(connection) => setEdges((eds) => addEdge(connection, eds)),
[setEdges]
);
return (
<div style={{ width: '100%', height: '100vh' }}>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
fitView
/>
</div>
);
}
import type { Node, Edge, NodeProps, BuiltInNode } from '@xyflow/react';
// Define custom node type with data shape
type CustomNode = Node<{ value: number; label: string }, 'custom'>;
// Combine with built-in nodes
type MyNode = CustomNode | BuiltInNode;
type MyEdge = Edge<{ weight?: number }>;
// Use throughout app
const [nodes, setNodes] = useNodesState<MyNode>(initialNodes);
import { memo } from 'react';
import { Handle, Position, type NodeProps } from '@xyflow/react';
// Define node type
type CounterNode = Node<{ count: number }, 'counter'>;
// Always wrap in memo for performance
const CounterNode = memo(function CounterNode({ data, isConnectable }: NodeProps<CounterNode>) {
return (
<>
<Handle type="target" position={Position.Top} isConnectable={isConnectable} />
<div className="counter-node">
Count: {data.count}
{/* nodrag prevents dragging when interacting with button */}
<button className="nodrag" onClick={() => console.log('clicked')}>
Increment
</button>
</div>
<Handle type="source" position={Position.Bottom} isConnectable={isConnectable} />
</>
);
});
// Register in nodeTypes (define OUTSIDE component to avoid re-renders)
const nodeTypes = { counter: CounterNode };
// Use in ReactFlow
<ReactFlow nodeTypes={nodeTypes} ... />
// Use handle IDs when a node has multiple handles of same type
<Handle type="source" position={Position.Right} id="a" />
<Handle type="source" position={Position.Right} id="b" style={{ top: 20 }} />
// Connect with specific handles
const edge = {
id: 'e1-2',
source: '1',
sourceHandle: 'a',
target: '2',
targetHandle: null
};
import { BaseEdge, EdgeProps, getSmoothStepPath } from '@xyflow/react';
function CustomEdge({ id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, data }: EdgeProps) {
const [edgePath, labelX, labelY] = getSmoothStepPath({
sourceX, sourceY, sourcePosition,
targetX, targetY, targetPosition,
});
return (
<>
<BaseEdge id={id} path={edgePath} />
<text x={labelX} y={labelY} className="edge-label">{data?.label}</text>
</>
);
}
const edgeTypes = { custom: CustomEdge };
// External state with change handlers
const [nodes, setNodes] = useState<Node[]>(initialNodes);
const [edges, setEdges] = useState<Edge[]>(initialEdges);
const onNodesChange = useCallback(
(changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
[]
);
const onEdgesChange = useCallback(
(changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),
[]
);
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
/>
import { useReactFlow, ReactFlowProvider } from '@xyflow/react';
function FlowControls() {
const {
getNodes, setNodes, addNodes, updateNodeData,
getEdges, setEdges, addEdges,
fitView, zoomIn, zoomOut, setViewport,
deleteElements, toObject,
} = useReactFlow();
const addNode = () => {
addNodes({ id: `${Date.now()}`, position: { x: 100, y: 100 }, data: { label: 'New' } });
};
return <button onClick={addNode}>Add Node</button>;
}
// Must wrap in provider when using useReactFlow
function App() {
return (
<ReactFlowProvider>
<Flow />
<FlowControls />
</ReactFlowProvider>
);
}
const { updateNodeData } = useReactFlow();
// Merge with existing data
updateNodeData(nodeId, { label: 'Updated' });
// Replace data entirely
updateNodeData(nodeId, { newField: 'value' }, { replace: true });
// Fit on initial render
<ReactFlow fitView fitViewOptions={{ padding: 0.2, maxZoom: 1 }} />
// Programmatic control
const { fitView, setViewport, getViewport, zoomTo } = useReactFlow();
// Fit to specific nodes
fitView({ nodes: [{ id: '1' }, { id: '2' }], duration: 500 });
// Set exact viewport
setViewport({ x: 100, y: 100, zoom: 1.5 }, { duration: 300 });
const isValidConnection = useCallback((connection: Connection) => {
// Prevent self-connections
if (connection.source === connection.target) return false;
// Custom validation logic
const sourceNode = getNode(connection.source);
const targetNode = getNode(connection.target);
return sourceNode?.type !== targetNode?.type;
}, []);
<ReactFlow isValidConnection={isValidConnection} />
<ReactFlow
// Core data
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
// Custom types (define OUTSIDE component)
nodeTypes={nodeTypes}
edgeTypes={edgeTypes}
// Connections
onConnect={onConnect}
connectionMode={ConnectionMode.Loose} // Allow target-to-target
isValidConnection={isValidConnection}
// Viewport
fitView
minZoom={0.1}
maxZoom={4}
defaultViewport={{ x: 0, y: 0, zoom: 1 }}
// Interaction
nodesDraggable={true}
nodesConnectable={true}
elementsSelectable={true}
panOnDrag={true}
zoomOnScroll={true}
// Additional components
<MiniMap />
<Controls />
<Background variant={BackgroundVariant.Dots} />
</ReactFlow>
| Class | Effect |
|---|---|
nodrag | Prevent dragging when clicking element |
nowheel | Prevent zoom on wheel events |
nopan | Prevent panning from element |
nokey | Prevent keyboard events (use on inputs) |
See ADDITIONAL_COMPONENTS.md for MiniMap, Controls, Background, NodeToolbar, NodeResizer.
Weekly Installs
148
Repository
GitHub Stars
41
First Seen
Jan 20, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode126
gemini-cli123
codex122
github-copilot112
cursor106
claude-code96
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
116,600 周安装