> 技术文档 > ReactFlow节点与边的动态交互实现

ReactFlow节点与边的动态交互实现


1、根据上一篇文章React Flow快速上手教程-CSDN博客构建好reate xyflow项目后,将其中的Flow.js文件内容替换为以下脚本,包含添加/删除节点和边、修改节点名称的功能

import React, { useState, useCallback, useRef, useEffect } from \'react\';import { ReactFlow, MiniMap, Controls, Background, useNodesState, useEdgesState, addEdge, MarkerType} from \'@xyflow/react\';import \'@xyflow/react/dist/style.css\';function Flow() { // 初始节点数据 const initialNodes = [ { id: \'1\', position: { x: 250, y: 5 }, data: { label: \'Node 1\' } }, { id: \'2\', position: { x: 100, y: 100 }, data: { label: \'Node 2\' } }, ]; // 使用 XYFlow 的状态管理钩子 const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); const [edges, setEdges, onEdgesChange] = useEdgesState([]); // 选中的节点状态(用于创建边) const [selectedNodes, setSelectedNodes] = useState({ source: null, target: null }); // 当前选中的元素(用于删除/重命名) const [selectedElement, setSelectedElement] = useState(null); // 节点重命名相关状态 const [isRenaming, setIsRenaming] = useState(false); const [newNodeName, setNewNodeName] = useState(\'\'); const renameInputRef = useRef(null); /** * 处理节点点击事件 * @param {Event} event - 点击事件对象 * @param {Object} node - 被点击的节点数据 */ const handleNodeClick = (event, node) => { // 如果正在重命名,则不做其他处理 if (isRenaming) return; setSelectedElement(node); // 选择源节点或目标节点 if (!selectedNodes.source) { setSelectedNodes({ source: node.id, target: null }); } else if (!selectedNodes.target && node.id !== selectedNodes.source) { setSelectedNodes(prev => ({ ...prev, target: node.id })); } else { setSelectedNodes({ source: node.id, target: null }); } }; /** * 处理边点击事件 * @param {Event} event - 点击事件对象 * @param {Object} edge - 被点击的边数据 */ const handleEdgeClick = (event, edge) => { if (isRenaming) return; setSelectedElement(edge); }; /** * 处理画布点击事件(点击空白处取消选择) */ const handlePaneClick = () => { if (isRenaming) return; setSelectedElement(null); }; /** * 添加新节点 */ const handleAddNode = () => { const newNodeId = `${nodes.length + 1}`; const newNode = { id: newNodeId, position: { x: Math.random() * 400, y: Math.random() * 400 }, data: { label: `Node ${newNodeId}` }, }; setNodes((nds) => [...nds, newNode]); }; /** * 添加新边(连接两个节点) */ const handleAddEdge = () => { if (selectedNodes.source && selectedNodes.target) { const newEdge = { id: `edge-${selectedNodes.source}-${selectedNodes.target}-${edges.length}`, source: selectedNodes.source, target: selectedNodes.target, // 边样式配置 markerEnd: { type: MarkerType.ArrowClosed, }, }; setEdges((eds) => addEdge(newEdge, eds)); setSelectedNodes({ source: null, target: null }); } }; /** * 删除选中的元素(节点或边) */ const handleDeleteElement = () => { if (!selectedElement || isRenaming) return; // 删除节点(会自动删除相关联的边) if (selectedElement.type === \'node\' || selectedElement.hasOwnProperty(\'data\')) { setNodes((nds) => nds.filter((node) => node.id !== selectedElement.id)); } // 删除边 else if (selectedElement.type === \'edge\' || selectedElement.hasOwnProperty(\'source\')) { setEdges((eds) => eds.filter((edge) => edge.id !== selectedElement.id)); } setSelectedElement(null); }; /** * 开始重命名节点 */ const startRenaming = () => { if (!selectedElement || !selectedElement.data) return; setIsRenaming(true); setNewNodeName(selectedElement.data.label); }; /** * 完成重命名 */ const finishRenaming = () => { if (!selectedElement || !selectedElement.data) return; setNodes(nodes.map(node => { if (node.id === selectedElement.id) { return { ...node, data: { ...node.data, label: newNodeName } }; } return node; })); setIsRenaming(false); setNewNodeName(\'\'); }; /** * 取消重命名 */ const cancelRenaming = () => { setIsRenaming(false); setNewNodeName(\'\'); }; /** * 处理重命名输入框的键盘事件 * @param {Event} e - 键盘事件对象 */ const handleRenameKeyDown = (e) => { if (e.key === \'Enter\') { finishRenaming(); } else if (e.key === \'Escape\') { cancelRenaming(); } }; // 自动聚焦到重命名输入框 useEffect(() => { if (isRenaming && renameInputRef.current) { renameInputRef.current.focus(); renameInputRef.current.select(); } }, [isRenaming]); /** * XYFlow 的连接回调(拖拽创建边时触发) */ const onConnect = useCallback( (params) => { const newEdge = { ...params, id: `edge-${params.source}-${params.target}-${edges.length}`, markerEnd: { type: MarkerType.ArrowClosed, }, }; return setEdges((eds) => addEdge(newEdge, eds)); }, [setEdges, edges.length] ); return ( <div style={{ width: \'100%\', height: \'100vh\' }}>  { if (isRenaming) return;  if ([\'Delete\', \'Backspace\'].includes(event.key)) { handleDeleteElement(); } }} >    {/* 自定义节点渲染 - 添加重命名输入框 */} {isRenaming && selectedElement && ( <div style={{  position: \'absolute\',  top: selectedElement.position.y,  left: selectedElement.position.x,  zIndex: 10,  backgroundColor: \'white\',  padding: \'5px\',  borderRadius: \'3px\',  boxShadow: \'0 0 5px rgba(0,0,0,0.2)\' }} >  setNewNodeName(e.target.value)}  onKeyDown={handleRenameKeyDown}  onBlur={finishRenaming}  style={{ width: \'150px\', padding: \'5px\', border: \'1px solid #ddd\', borderRadius: \'3px\'  }} /> <div style={{ fontSize: \'12px\', color: \'#666\', marginTop: \'5px\' }}>  Press Enter to save, Esc to cancel 
)} {/* 控制面板 */} <div style={{ position: \'absolute\', top: 10, left: 10, zIndex: 5, display: \'flex\', gap: \'10px\', flexDirection: \'column\', backgroundColor: \'rgba(255, 255, 255, 0.9)\', padding: \'10px\', borderRadius: \'5px\', boxShadow: \'0 0 10px rgba(0,0,0,0.1)\' }}> <button onClick={handleDeleteElement} disabled={!selectedElement || isRenaming} style={{ backgroundColor: selectedElement && !isRenaming ? \'#ff6b6b\' : \'#ccc\' }} > Delete Selected <button onClick={startRenaming} disabled={!selectedElement || !selectedElement.data || isRenaming} style={{ backgroundColor: selectedElement?.data && !isRenaming ? \'#4dabf7\' : \'#ccc\' }} > Rename Node {/* 状态显示 */} <div style={{ marginTop: \'10px\', fontSize: \'14px\' }}>
Selected: {selectedElement?.id || \'None\'}
Current name: {selectedElement?.data?.label || \'-\'}
Source: {selectedNodes.source || \'Not selected\'}
Target: {selectedNodes.target || \'Not selected\'}
{isRenaming && <div style={{ color: \'#f08c00\', marginTop: \'5px\' }}>Renaming mode active
}
);}export default Flow;

这个基于 React 和 XYFlow 的流程图编辑器提供了完整的流程图创建、编辑和管理功能。以下是详细的功能描述和操作步骤:

2、主要功能

  1. 节点管理

  2. 边(连接线)管理

  3. 交互功能

  4. 视图控制

3、详细操作步骤

1. 添加新节点

  1. 点击左上角控制面板的 \"Add Node\" 按钮

  2. 新节点会随机出现在画布上,自动命名为 \"Node X\"(X为序号)

  3. 可以重复点击添加多个节点

2. 创建连接线(边)

方法一:点击选择+按钮创建

  1. 点击第一个节点(源节点) - 状态面板会显示 \"Source: [节点ID]\"

  2. 点击第二个节点(目标节点) - 状态面板会显示 \"Target: [节点ID]\"

  3. 点击 \"Add Edge\" 按钮创建连接线

方法二:拖拽创建(更快捷)

  1. 将鼠标悬停在源节点边缘,直到出现连接锚点

  2. 按住鼠标拖动到目标节点

  3. 释放鼠标完成连接创建

3. 重命名节点

  1. 点击选择要重命名的节点

  2. 点击控制面板的 \"Rename Node\" 按钮

  3. 在出现的输入框中输入新名称

  4. 按 Enter 确认或点击其他地方确认

4. 删除元素

  1. 点击选择要删除的节点或连接线

  2. 执行以下任一操作:

5. 调整视图

状态面板说明

控制面板下方显示当前状态信息:

使用提示

  1. 当按钮显示为灰色时表示该操作当前不可用

  2. 重命名模式下会暂时禁用其他操作

  3. 连接线会自动带有箭头指示方向

  4. 可以同时使用多种方式操作(如拖拽创建连接+按钮删除组合)

这个编辑器提供了直观的流程图创建体验,适合各种需要可视化流程的场景,如系统架构设计、工作流规划、算法可视化等。

运行截图:

网络标签: