本文為《React Agent:從零開始構建 AI 智能體》專欄系列文章。 專欄地址:https://blog.csdn.net/suiyingy/category_12933485.html。項目地址:https://gitee.com/fgai/react-agent(含完整代碼示?例與實戰源)。完整介紹:https://blog.csdn.net/suiyingy/article/details/146983582。
????????邊可以響應多種用戶操作,如點擊、雙擊、鼠標懸停等。通過綁定相應的事件處理函數,實現邊的交互功能。點擊邊彈出其詳細信息窗口;鼠標懸停時顯示工具提示,說明邊所代表的關系含義。
1 鼠標懸停
????????下面程序為鼠標懸停事件示例,顯示邊的信息、改變線條顏色、寬度和線型。
import React, { useCallback, useState } from 'react';
import {ReactFlow,useNodesState,useEdgesState,addEdge,
} from 'reactflow';
import 'reactflow/dist/style.css';const initialNodes = [{ id: '1', position: { x: 0, y: 0 }, data: { label: '1' } },{ id: '2', position: { x: 0, y: 100 }, data: { label: '2' } },
];const initialEdges = [{ id: 'e1-2', source: '1', target: '2',type: 'default' }
];export default function FlowComponent() {const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);const [hoveredEdgeId, setHoveredEdgeId] = useState(null);const onConnect = useCallback((params) => setEdges((eds) => addEdge(params, eds)),[setEdges]);// 動態獲取邊樣式const getEdgeStyle = (edge) => {return hoveredEdgeId === edge.id ? {stroke: 'red',strokeWidth: 3,strokeDasharray: '5 5',}: {};};return (<div style={{ height: '500px', width: '100%' }}><ReactFlownodes={nodes}edges={edges.map(edge => ({...edge,style: getEdgeStyle(edge)}))}onNodesChange={onNodesChange}onEdgesChange={onEdgesChange}onConnect={onConnect}onEdgeMouseEnter={(event, edge) => setHoveredEdgeId(edge.id)}onEdgeMouseLeave={() => setHoveredEdgeId(null)}fitView/></div>);
}
????????運行程序后結果如下圖所示。
圖1 邊 - 鼠標懸停
2 鼠標單擊
????????下面程序為鼠標單擊事件示例,顯示邊的信息。
export default function App() {const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);const onConnect = useCallback((params) => setEdges((eds) => addEdge(params, eds)),[setEdges],);const onEdgeClick = useCallback((event, edge) => {console.log('Clicked edge:', edge);// 這里可以添加更多邏輯,比如顯示模態框等}, []);return (<div style={{ height: '500px' }}><ReactFlownodes={nodes}edges={edges}onNodesChange={onNodesChange}onEdgesChange={onEdgesChange}onConnect={onConnect}onEdgeClick={onEdgeClick}fitView/></div>);
}
????????運行程序后結果如下圖所示。
圖2 邊 - 鼠標單擊
3 鼠標雙擊
????????下面程序為鼠標雙擊事件示例。
export default function App() {const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);const onConnect = useCallback((params) => setEdges((eds) => addEdge(params, eds)),[setEdges],);const handleEdgeDoubleClick = (event, edge) => {alert(`雙擊了邊:${edge.id}`);// 這里可以添加更多自定義邏輯,比如:// - 刪除邊// - 編輯邊屬性// - 高亮關聯節點等};return (<div style={{ height: '500px' }}><ReactFlownodes={nodes}edges={edges}onNodesChange={onNodesChange}onEdgesChange={onEdgesChange}onConnect={onConnect}onEdgeDoubleClick={handleEdgeDoubleClick}fitView/></div>);
}
4 鍵盤事件
????????同樣地,邊也支持鍵盤事件,示例如下:
export default function App() {const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);// 處理連接線const onConnect = useCallback((params) => setEdges((eds) => addEdge(params, eds)),[setEdges],);// 鍵盤事件處理const handleKeyDown = useCallback((event) => {if (event.key === 'Delete') {alert('鍵盤Delete鍵被按下')}}, [setNodes, setEdges]);return (<div style={{ height: '500px', outline: 'none' }} tabIndex={0} onKeyDown={handleKeyDown}><ReactFlownodes={nodes}edges={edges}onNodesChange={onNodesChange}onEdgesChange={onEdgesChange}onConnect={onConnect}fitView/></div>);
}
5 連接事件
????????在 React Flow 中,邊的連接和斷開是常見操作。當用戶嘗試連接兩個節點時,系統需要驗證連接的合法性,如檢查節點的輸入輸出端口是否匹配、是否存在循環連接等。斷開連接時需要處理相關的數據更新和視覺效果變化。可以通過onConnect 和 onEdgesDelete 事件進行自定義邏輯處理。
????????onConnect示例程序如下所示。
export default function App() {const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);const onConnect = useCallback((params) => {// 查找源節點和目標節點const sourceNode = nodes.find(node => node.id === params.source);const targetNode = nodes.find(node => node.id === params.target);// 打印節點信息console.log('連接起始端點:', sourceNode);console.log('連接終止端點:', targetNode);// 添加連接邊setEdges((eds) => addEdge(params, eds));},[setEdges, nodes] // 添加nodes依賴確保獲取最新數據);return (<div style={{ height: '500px' }}><ReactFlownodes={nodes}edges={edges}onNodesChange={onNodesChange}onEdgesChange={onEdgesChange}onConnect={onConnect}fitView/></div>);
}
????????運行程序后結果如下圖所示。
圖3 onConnect 連接
6 連接規則
????????我們也可以設置連接規則,例如下面程序不允許自身內部進行連接。
import React, { useCallback } from 'react';
import { ReactFlow, Handle, useNodesState, useEdgesState, addEdge } from 'reactflow';
import 'reactflow/dist/style.css';
import { FiDatabase, FiCloud } from 'react-icons/fi';
import { toast, Toaster } from 'react-hot-toast'; // 添加Toast組件// 自定義節點組件 npm install react-hot-toast
const CustomNode = ({ id, data, selected }) => {return (<div className={`custom-node ${selected ? 'selected' : ''}`}><Handletype="target"position="top"className="!bg-teal-500"// isValidConnection={(connection) => // connection.source !== id // 禁止自連接// }/><div className="node-header"><FiCloud className="node-icon" /><h3 className="node-title">{data.label}</h3></div><div className="node-body"><FiDatabase className="node-icon" /><span className="node-info">{data.content}</span></div><Handletype="source"position="bottom"className="!bg-purple-500"/><Handletype="source"position="right"id={`${id}-output-2`}className="!bg-pink-500"style={{ top: '30%' }}/></div>);
};const initialNodes = [{ id: '1', position: { x: 0, y: 0 }, data: { label: '開始節點',content: '輸入數據源'},type: 'custom',},{ id: '2', position: { x: 200, y: 150 }, data: { label: '處理節點',content: '數據處理流程'},type: 'custom',},
];const initialEdges = [{ id: 'e1-2', source: '1', target: '2',animated: true,style: { stroke: '#94a3b8' },
}];const nodeTypes = {custom: CustomNode,
};// 節點樣式
const nodeStyle = `.custom-node {background: linear-gradient(145deg, #ffffff, #f1f5f9);border-radius: 8px;border: 2px solid #cbd5e1;box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);padding: 16px;min-width: 200px;transition: all 0.2s ease;}.custom-node.selected {border-color: #6366f1;box-shadow: 0 4px 15px rgba(99, 102, 241, 0.2);}.custom-node:hover {transform: translateY(-2px);}.node-header {display: flex;align-items: center;margin-bottom: 12px;border-bottom: 1px solid #e2e8f0;padding-bottom: 8px;}.node-title {margin: 0;font-size: 1.1rem;color: #1e293b;margin-left: 8px;}.node-body {display: flex;align-items: center;color: #64748b;}.node-icon {font-size: 1.2rem;margin-right: 8px;color: #6366f1;}.node-info {font-size: 0.9rem;}.react-flow__handle {width: 14px;height: 14px;border-radius: 3px;border: none;}
`;export default function App() {// 使用useNodesState管理節點狀態const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);// 連接處理回調const onConnect = useCallback((connection) => {// 根據不同的輸出端口設置邊樣式const edgeStyle = connection.sourceHandle?.endsWith('-output-2') ? { stroke: '#ec4899' } : { stroke: '#94a3b8' };return setEdges((eds) =>addEdge({...connection,animated: true,style: edgeStyle,}, eds));},[setEdges]);// 連接驗證邏輯const isValidConnection = useCallback((connection) => {// 禁止自連接if (connection.source === connection.target) {toast.error('不能連接到自身');// alert('不能連接到自身');return false;}// 檢查目標節點是否已有連接const targetConnections = edges.filter((edge) => edge.target === connection.target);if (targetConnections.length > 0) {toast.error('目標節點已有連接');console.log(`連接被禁止:節點 ${connection.target} 已有輸入連接`);return false;}return true;},[edges]);return (<div style={{ height: '100vh', background: '#f8fafc' }}><style>{nodeStyle}</style><Toaster position="top-right" /> {/* Toast消息容器 */}<ReactFlow nodes={nodes}edges={edges}onNodesChange={onNodesChange} // 添加狀態變更處理器onEdgesChange={onEdgesChange}onConnect={onConnect}nodeTypes={nodeTypes}isValidConnection={isValidConnection}fitViewstyle={{ background: '#f8fafc' }}connectionLineStyle={{ stroke: '#94a3b8', strokeWidth: 2 }}defaultEdgeOptions={{type: 'smoothstep',animated: true,style: { strokeWidth: 2 }}}/></div>);
}
7 斷開事件
????????onEdgesDelete 示例程序如下所示。
export default function App() {const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);// 連接處理const onConnect = useCallback((params) => {const sourceNode = nodes.find(n => n.id === params.source);const targetNode = nodes.find(n => n.id === params.target);console.log('[連接建立] 起始節點:', sourceNode);console.log('[連接建立] 終止節點:', targetNode);setEdges((eds) => addEdge(params, eds));},[setEdges, nodes]);// 斷開連接處理const onEdgesDeleted = useCallback((deletedEdges) => {deletedEdges.forEach(edge => {const sourceNode = nodes.find(n => n.id === edge.source);const targetNode = nodes.find(n => n.id === edge.target);console.log('[連接斷開] 起始節點:', sourceNode);console.log('[連接斷開] 終止節點:', targetNode);});},[nodes]);return (<div style={{ height: '500px' }}><ReactFlownodes={nodes}edges={edges}onNodesChange={onNodesChange}onEdgesChange={onEdgesChange}onConnect={onConnect}onEdgesDelete={onEdgesDeleted} // 添加斷開連接處理器fitView/></div>);
}
????????運行程序后結果如下圖所示。
圖4 onEdgesDelete 連接斷開
立即關注獲取最新動態
點擊訂閱《React Agent 開發專欄》,每周獲取智能體開發深度教程。項目代碼持續更新至React Agent 開源倉庫,歡迎 Star 獲取實時更新通知!FGAI 人工智能平臺:FGAI 人工智能平臺