文章目錄
- ?前言
- ?引入react-flow
- ?自定義節點nodeType
- ?自定義邊edgeType
- ?添加節點
- ?inscode代碼塊
- ?結束
?前言
大家好,我是yma16,本文分享 前端 ——react_flow自定義節點、邊——使用darg布局樹狀結構。
自定義效果
可以自定義節點、邊、線條流動
React Flow 簡介
React Flow 是一個用于構建交互式節點和基于圖的編輯器的開源 React 庫。它專為可視化復雜工作流、流程圖、狀態機或依賴關系而設計,提供高度可定制化的節點、連線(edges)和交互功能。
node系列往期文章
node_windows環境變量配置
node_npm發布包
linux_配置node
node_nvm安裝配置
node筆記_http服務搭建(渲染html、json)
node筆記_讀文件
node筆記_寫文件
node筆記_連接mysql實現crud
node筆記_formidable實現前后端聯調的文件上傳
node筆記_koa框架介紹
node_koa路由
node_生成目錄
node_讀寫excel
node筆記_讀取目錄的文件
node筆記——調用免費qq的smtp發送html格式郵箱
node實戰——搭建帶swagger接口文檔的后端koa項目(node后端就業儲備知識)
node實戰——后端koa結合jwt連接mysql實現權限登錄(node后端就業儲備知識)
node實戰——koa給郵件發送驗證碼并緩存到redis服務(node后端儲備知識)
koa系列項目文章
前端vite+vue3結合后端node+koa——實現代碼模板展示平臺(支持模糊搜索+分頁查詢)
node+vue3+mysql前后分離開發范式——實現對數據庫表的增刪改查
node+vue3+mysql前后分離開發范式——實現視頻文件上傳并渲染
koa-vue性能監控到封裝sdk系列文章
性能監控系統搭建——node_koa實現性能監控數據上報(第一章)
性能監控系統搭建——vue3實現性能監控數據展示(第二章)
性能監控計算——封裝帶性能計算并上報的npm包(第三章)
canvas系列文章
web canvas系列——快速入門上手繪制二維空間點、線、面
webgl canvas系列——快速加背景、摳圖、加水印并下載圖片
webgl canvas系列——animation中基本旋轉、平移、縮放(模擬冒泡排序過程)
前端vue系列文章
vue3 + fastapi 實現選擇目錄所有文件自定義上傳到服務器
前端vue2、vue3去掉url路由“ # ”號——nginx配置
csdn新星計劃vue3+ts+antd賽道——利用inscode搭建vue3(ts)+antd前端模板
認識vite_vue3 初始化項目到打包
python_selenuim獲取csdn新星賽道選手所在城市用echarts地圖顯示
讓大模型分析csdn文章質量 —— 提取csdn博客評論在文心一言分析評論區內容
前端vue3——html2canvas給網站截圖生成宣傳海報
前端——html拖拽原理
前端 富文本編輯器原理——從javascript、html、css開始入門
前端老古董execCommand——操作 選中文本 樣式
前端如何在30秒內實現吸管拾色器?
前端——原生Selection api操作選中文本 樣式、取消樣式(解決標簽的無限嵌套問題)
前端 ——xml轉json json轉xml 實現 mjml 郵件內容轉json,json轉mjml
前端 ——youtube、tiktok視頻封面獲取并使用canvas合并封面和自定義播放按鈕生成圖片
前端gmail郵件加載動態樣式——動態評分交互郵件可提交api
?引入react-flow
安裝@xyflow/react
pnpm add @xyflow/react
基礎使用
import React from 'react';
import { ReactFlow } from '@xyflow/react';import '@xyflow/react/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' }];export default function App() {return (<div style={{ width: '100vw', height: '100vh' }}><ReactFlow nodes={initialNodes} edges={initialEdges} /></div>);
}
?自定義節點nodeType
定義一個BaseNode ,可以引用flow的handle展示線條的連接點
import { Handle, Position, NodeToolbar } from '@xyflow/react';
import React, { useEffect } from 'react';
import './style.scss';const BaseNode = (props: any) => {const { data } = props;useEffect(() => {console.log('props', props);}, [props]);return (<div className="base-node"><NodeToolbar isVisible={data.toolbarVisible} position={Position.Top}><button>toolbar 按鈕</button></NodeToolbar><div style={{ padding: '10px 20px' }}>{data.label}</div>{/* {data.customDataType !== 'start' ? <Handle type="source" position={Position.Top} /> : ''} */}{data.customDataType !== 'end' ? <Handle type="source" position={Position.Bottom} /> : ""}{data.customDataType !== 'start' ? <Handle type="target" position={Position.Top} /> : ""}</div >);
};export default BaseNode;
flow組件nodeType引入BaseNode
const nodeTypes = useMemo(() => {return { textUpdater: TextUpdaterNode, AddNode: AddNode, baseNode: BaseNode };}, [TextUpdaterNode, AddNode, BaseNode]);
節點引用 type: ‘baseNode’
// 初始節點
export const initialNodes = [{id: 'start_id',position: { x: 0, y: 0 },data: { label: `開始節點_${new Date().Format('yyyy-MM-dd hh:mm:ss')}`, customDataType: 'start' },type: 'baseNode'},{id: 'test_id',position: { x: 0, y: 0 },data: { label: `測試節點_${new Date().Format('yyyy-MM-dd hh:mm:ss')}`, customDataType: 'test' },type: 'baseNode'},{id: 'end_id',position: { x: 0, y: 0 },data: { label: `結束節點_${new Date().Format('yyyy-MM-dd hh:mm:ss')}`, customDataType: 'end' },type: 'baseNode'}
];
?自定義邊edgeType
自定義邊——添加按鈕
import {BaseEdge,EdgeLabelRenderer,getStraightPath,getSmoothStepPath,getSimpleBezierPath,Position,useReactFlow,
} from '@xyflow/react';import './style.scss'
import React, { useState } from 'react';export default function CustomAddEdge(props: any) {const { id, sourceX, sourceY, targetX, targetY, data } = props;console.log('CustomAddEdge props', props);console.log('CustomAddEdge props data', data);const [isShowAddPanel, setIsShowAddPanel] = useState(false);const [isSelectEdge, setIsSelectEdge] = useState(false);const [path,] = getSimpleBezierPath({sourceX: sourceX,sourceY: sourceY,// sourcePosition: Position.Top,targetX: targetX,targetY: targetY,// targetPosition: Position.Bottom,});const [edgePath, labelX, labelY] = getStraightPath({sourceX,sourceY,targetX,targetY,});return (<><BaseEdge id={id} path={path} /><circle r="10" fill="#ff0073"><animateMotion dur="2s" repeatCount="indefinite" path={edgePath} /></circle><EdgeLabelRenderer><buttonstyle={{position: 'absolute',transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,pointerEvents: 'all',borderRadius: '50px',cursor: 'pointer',}}className="add-button"onClick={() => {setIsSelectEdge(true)console.log('add button clicked', props);setIsShowAddPanel(!isShowAddPanel);}}>+<div style={{display: isShowAddPanel ? 'block' : 'none',}} className='add-button-edge-panel-container'><div ><button onClick={() => {console.log('添加普通節點');data?.onAddBaseNode?.({curEdgeId: id});}} className='add-button-edge-panel'>添加普通節點</button></div><div><br></br></div><div ><button onClick={() => {console.log('添加分支節點 left');data?.onAddBranchNode?.({curEdgeId: id,direction: 'left'});}} className='add-button-edge-panel'>添加分支 保留左邊</button></div><div><br></br></div><div ><button onClick={() => {console.log('添加分支節點 right');data?.onAddBranchNode?.({curEdgeId: id,direction: 'right'});}} className='add-button-edge-panel'>添加分支 保留右邊</button></div></div></button></EdgeLabelRenderer></>);
}
?添加節點
基礎節點
// 添加基礎節點
export const addBaseNode = (config: { curEdgeId: string; nodes: any[]; edges: any[] }) => {const { curEdgeId, nodes, edges } = config;console.log('addBaseNode curEdgeId', curEdgeId);// 當前邊的節點const curEdge = edges.find(edge => edge.id === curEdgeId);if (!curEdge) {console.error('Edge not found for id:', curEdgeId);return { nodes, edges };}// 創建新的節點 基礎節點const virtualNode = {id: customGenUuid(),position: { x: 0, y: 0 },data: { label: `普通節點_${new Date().Format('yyyy-MM-dd hh:mm:ss')}`, customDataType: 'test' },type: 'baseNode'};// 新節點const newNodes: any[] = [];// 遍歷節點添加 按順序加入nodes.forEach(node => {if (node.id === curEdge.source) {// 在當前邊的源節點后面添加新節點newNodes.push(virtualNode);}newNodes.push(node);});// 添加新邊const newEdges: any[] = [];edges.forEach(edge => {// 如果是當前邊,則添加新邊 source和target 中間添加一個節點 補充一條邊if (edge.id === curEdgeId) {// 在當前邊后面添加新邊newEdges.push({id: customGenUuid(),source: curEdge.source,// 鏈接當前節點target: virtualNode.id,type: 'customAddEdge',markerEnd: {type: MarkerType.Arrow}});// 在當前邊后面添加新邊newEdges.push({id: customGenUuid(),source: virtualNode.id,// 鏈接當前節點target: curEdge.target,type: 'customAddEdge',markerEnd: {type: MarkerType.Arrow}});} else {// 其他邊不變newEdges.push(edge);}});return {newNodes: newNodes,newEdges: newEdges};
};
添加分支節點
// 添加分支節點 默認添加左分支
export const addBranchNode = (config: { curEdgeId: string; nodes: any[]; edges: any[]; direction: string }) => {const { curEdgeId, nodes, edges, direction } = config;console.log('addBaseNode curEdgeId', curEdgeId);// 當前邊的節點const curEdge = edges.find(edge => edge.id === curEdgeId);if (!curEdge) {console.error('Edge not found for id:', curEdgeId);return { nodes, edges };}// 創建新的節點 基礎節點const virtualLeftNode = {id: customGenUuid(),position: { x: 0, y: 0 },// 左邊分支 節點data: { label: `分支節點_${new Date().Format('yyyy-MM-dd hh:mm:ss')}`, customDataType: 'test', branchDirection: 'left' },type: 'baseNode'};// 右邊分支節點const virtualRightNode = {id: customGenUuid(),position: { x: 0, y: 0 },// 左邊分支 節點data: { label: `分支節點_${new Date().Format('yyyy-MM-dd hh:mm:ss')}`, customDataType: 'test', branchDirection: 'right' },type: 'baseNode'};// 新節點const newNodes: any[] = [];// 遍歷節點添加 按順序加入nodes.forEach(node => {if (node.id === curEdge.source) {// 在當前邊的源節點后面添加新節點 先添加左邊在添加右邊的節點if (direction === 'left') {newNodes.push(virtualLeftNode);newNodes.push(virtualRightNode);} else {// 右邊分支newNodes.push(virtualRightNode);newNodes.push(virtualLeftNode);}}newNodes.push(node);});// 添加新邊const newEdges: any[] = [];edges.forEach(edge => {// 如果是當前邊,則添加新邊 source和target 中間添加一個節點 補充一條邊if (edge.id === curEdgeId) {// 在當前邊后面添加新邊newEdges.push({id: customGenUuid(),source: curEdge.source,// 鏈接當前節點target: direction === 'left' ? virtualLeftNode.id : virtualRightNode.id,data: {branchDirection: 'right'},type: 'customAddEdge',markerEnd: {type: MarkerType.Arrow}});// 在當前邊后面添加新邊newEdges.push({id: customGenUuid(),source: direction === 'left' ? virtualLeftNode.id : virtualRightNode.id,// 鏈接當前節點target: curEdge.target,data: {branchDirection: 'right'},type: 'customAddEdge',markerEnd: {type: MarkerType.Arrow}});// 添加右側分支邊newEdges.push({id: customGenUuid(),source: curEdge.source,// 鏈接當前節點target: direction === 'left' ? virtualRightNode.id : virtualLeftNode.id,type: 'customAddEdge',data: {branchDirection: 'right'},markerEnd: {type: MarkerType.Arrow}});} else {// 其他邊不變newEdges.push(edge);}});console.log('addBranchNode newNodes', {newNodes: newNodes,newEdges: newEdges});return {newNodes: newNodes,newEdges: newEdges};
};
?inscode代碼塊
效果演示
react_flow特點
節點與連線:支持自定義節點(矩形、圓形等)和動態連線(貝塞爾曲線、直線等)。
交互功能:拖拽節點、縮放畫布、選擇多個元素、鍵盤快捷鍵支持。
狀態管理:與外部狀態庫(如 Redux、Zustand)無縫集成。
插件系統:內置背景網格、迷你地圖、節點工具欄等插件。
性能優化:僅渲染可見區域的節點,適合大規模數據場景。
react-flow存在的問題
React Flow在處理大規模節點和連線時可能出現性能瓶頸,尤其是當畫布包含數百個節點時,渲染和交互可能變得遲緩。動態添加或刪除節點時的重繪操作可能消耗較多資源。
?結束
本文分享到這結束,如有錯誤或者不足之處歡迎指出!
👍 點贊,是我創作的動力!
?? 收藏,是我努力的方向!
?? 評論,是我進步的財富!
💖 最后,感謝你的閱讀!