react-flow-architect
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseReactFlow Architect
ReactFlow 架构指南
Build production-ready ReactFlow applications with hierarchical navigation, performance optimization, and advanced state management.
构建具备层级导航、性能优化和高级状态管理的生产级ReactFlow应用。
Quick Start
快速入门
Create basic interactive graph:
tsx
import ReactFlow, { Node, Edge } from "reactflow";
const nodes: Node[] = [
{ id: "1", position: { x: 0, y: 0 }, data: { label: "Node 1" } },
{ id: "2", position: { x: 100, y: 100 }, data: { label: "Node 2" } },
];
const edges: Edge[] = [{ id: "e1-2", source: "1", target: "2" }];
export default function Graph() {
return <ReactFlow nodes={nodes} edges={edges} />;
}创建基础交互式图形:
tsx
import ReactFlow, { Node, Edge } from "reactflow";
const nodes: Node[] = [
{ id: "1", position: { x: 0, y: 0 }, data: { label: "Node 1" } },
{ id: "2", position: { x: 100, y: 100 }, data: { label: "Node 2" } },
];
const edges: Edge[] = [{ id: "e1-2", source: "1", target: "2" }];
export default function Graph() {
return <ReactFlow nodes={nodes} edges={edges} />;
}Core Patterns
核心模式
Hierarchical Tree Navigation
层级树导航
Build expandable/collapsible tree structures with parent-child relationships.
构建带有父子关系的可展开/折叠树结构。
Node Schema
节点 Schema
typescript
interface TreeNode extends Node {
data: {
label: string;
level: number;
hasChildren: boolean;
isExpanded: boolean;
childCount: number;
category: "root" | "category" | "process" | "detail";
};
}typescript
interface TreeNode extends Node {
data: {
label: string;
level: number;
hasChildren: boolean;
isExpanded: boolean;
childCount: number;
category: "root" | "category" | "process" | "detail";
};
}Incremental Node Building
增量节点构建
typescript
const buildVisibleNodes = useCallback(
(allNodes: TreeNode[], expandedIds: Set<string>, otherDeps: any[]) => {
const visibleNodes = new Map<string, TreeNode>();
const visibleEdges = new Map<string, TreeEdge>();
// Start with root nodes
const rootNodes = allNodes.filter((n) => n.data.level === 0);
// Recursively add visible nodes
const addVisibleChildren = (node: TreeNode) => {
visibleNodes.set(node.id, node);
if (expandedIds.has(node.id)) {
const children = allNodes.filter((n) => n.parentNode === node.id);
children.forEach((child) => addVisibleChildren(child));
}
};
rootNodes.forEach((root) => addVisibleChildren(root));
return {
nodes: Array.from(visibleNodes.values()),
edges: Array.from(visibleEdges.values()),
};
},
[],
);typescript
const buildVisibleNodes = useCallback(
(allNodes: TreeNode[], expandedIds: Set<string>, otherDeps: any[]) => {
const visibleNodes = new Map<string, TreeNode>();
const visibleEdges = new Map<string, TreeEdge>();
// Start with root nodes
const rootNodes = allNodes.filter((n) => n.data.level === 0);
// Recursively add visible nodes
const addVisibleChildren = (node: TreeNode) => {
visibleNodes.set(node.id, node);
if (expandedIds.has(node.id)) {
const children = allNodes.filter((n) => n.parentNode === node.id);
children.forEach((child) => addVisibleChildren(child));
}
};
rootNodes.forEach((root) => addVisibleChildren(root));
return {
nodes: Array.from(visibleNodes.values()),
edges: Array.from(visibleEdges.values()),
};
},
[],
);Performance Optimization
性能优化
Handle large datasets with incremental rendering and memoization.
通过增量渲染和记忆化处理大数据集。
Incremental Rendering
增量渲染
typescript
const useIncrementalGraph = (
allNodes: Node[],
allEdges: Edge[],
expandedList: string[],
) => {
const prevExpandedListRef = useRef<Set<string>>(new Set());
const prevOtherDepsRef = useRef<any[]>([]);
const { visibleNodes, visibleEdges } = useMemo(() => {
const currentExpandedSet = new Set(expandedList);
const prevExpandedSet = prevExpandedListRef.current;
// Check if expanded list changed
const expandedChanged = !areSetsEqual(currentExpandedSet, prevExpandedSet);
// Check if other dependencies changed
const otherDepsChanged = !arraysEqual(otherDeps, prevOtherDepsRef.current);
if (expandedChanged && !otherDepsChanged) {
// Only expanded list changed - incremental update
return buildIncrementalUpdate(
cachedVisibleNodesRef.current,
cachedVisibleEdgesRef.current,
allNodes,
allEdges,
currentExpandedSet,
prevExpandedSet,
);
} else {
// Full rebuild needed
return buildFullGraph(allNodes, allEdges, currentExpandedSet);
}
}, [allNodes, allEdges, expandedList, ...otherDeps]);
return { visibleNodes, visibleEdges };
};typescript
const useIncrementalGraph = (
allNodes: Node[],
allEdges: Edge[],
expandedList: string[],
) => {
const prevExpandedListRef = useRef<Set<string>>(new Set());
const prevOtherDepsRef = useRef<any[]>([]);
const { visibleNodes, visibleEdges } = useMemo(() => {
const currentExpandedSet = new Set(expandedList);
const prevExpandedSet = prevExpandedListRef.current;
// Check if expanded list changed
const expandedChanged = !areSetsEqual(currentExpandedSet, prevExpandedSet);
// Check if other dependencies changed
const otherDepsChanged = !arraysEqual(otherDeps, prevOtherDepsRef.current);
if (expandedChanged && !otherDepsChanged) {
// Only expanded list changed - incremental update
return buildIncrementalUpdate(
cachedVisibleNodesRef.current,
cachedVisibleEdgesRef.current,
allNodes,
allEdges,
currentExpandedSet,
prevExpandedSet,
);
} else {
// Full rebuild needed
return buildFullGraph(allNodes, allEdges, currentExpandedSet);
}
}, [allNodes, allEdges, expandedList, ...otherDeps]);
return { visibleNodes, visibleEdges };
};Memoization Patterns
记忆化模式
typescript
// Memoize node components to prevent unnecessary re-renders
const ProcessNode = memo(({ data, selected }: NodeProps) => {
return (
<div className={`process-node ${selected ? 'selected' : ''}`}>
{data.label}
</div>
);
}, (prevProps, nextProps) => {
// Custom comparison function
return (
prevProps.data.label === nextProps.data.label &&
prevProps.selected === nextProps.selected &&
prevProps.data.isExpanded === nextProps.data.isExpanded
);
});
// Memoize edge calculations
const styledEdges = useMemo(() => {
return edges.map(edge => ({
...edge,
style: {
...edge.style,
strokeWidth: selectedEdgeId === edge.id ? 3 : 2,
stroke: selectedEdgeId === edge.id ? '#3b82f6' : '#94a3b8',
},
animated: selectedEdgeId === edge.id,
}));
}, [edges, selectedEdgeId]);typescript
// Memoize node components to prevent unnecessary re-renders
const ProcessNode = memo(({ data, selected }: NodeProps) => {
return (
<div className={`process-node ${selected ? 'selected' : ''}`}>
{data.label}
</div>
);
}, (prevProps, nextProps) => {
// Custom comparison function
return (
prevProps.data.label === nextProps.data.label &&
prevProps.selected === nextProps.selected &&
prevProps.data.isExpanded === nextProps.data.isExpanded
);
});
// Memoize edge calculations
const styledEdges = useMemo(() => {
return edges.map(edge => ({
...edge,
style: {
...edge.style,
strokeWidth: selectedEdgeId === edge.id ? 3 : 2,
stroke: selectedEdgeId === edge.id ? '#3b82f6' : '#94a3b8',
},
animated: selectedEdgeId === edge.id,
}));
}, [edges, selectedEdgeId]);State Management
状态管理
Complex node/edge state patterns with undo/redo and persistence.
带有撤销/重做和持久化功能的复杂节点/边状态模式。
Reducer Pattern
Reducer 模式
typescript
type GraphAction =
| { type: "SELECT_NODE"; payload: string }
| { type: "SELECT_EDGE"; payload: string }
| { type: "TOGGLE_EXPAND"; payload: string }
| { type: "UPDATE_NODES"; payload: Node[] }
| { type: "UPDATE_EDGES"; payload: Edge[] }
| { type: "UNDO" }
| { type: "REDO" };
const graphReducer = (state: GraphState, action: GraphAction): GraphState => {
switch (action.type) {
case "SELECT_NODE":
return {
...state,
selectedNodeId: action.payload,
selectedEdgeId: null,
};
case "TOGGLE_EXPAND":
const newExpanded = new Set(state.expandedNodeIds);
if (newExpanded.has(action.payload)) {
newExpanded.delete(action.payload);
} else {
newExpanded.add(action.payload);
}
return {
...state,
expandedNodeIds: newExpanded,
isDirty: true,
};
default:
return state;
}
};typescript
type GraphAction =
| { type: "SELECT_NODE"; payload: string }
| { type: "SELECT_EDGE"; payload: string }
| { type: "TOGGLE_EXPAND"; payload: string }
| { type: "UPDATE_NODES"; payload: Node[] }
| { type: "UPDATE_EDGES"; payload: Edge[] }
| { type: "UNDO" }
| { type: "REDO" };
const graphReducer = (state: GraphState, action: GraphAction): GraphState => {
switch (action.type) {
case "SELECT_NODE":
return {
...state,
selectedNodeId: action.payload,
selectedEdgeId: null,
};
case "TOGGLE_EXPAND":
const newExpanded = new Set(state.expandedNodeIds);
if (newExpanded.has(action.payload)) {
newExpanded.delete(action.payload);
} else {
newExpanded.add(action.payload);
}
return {
...state,
expandedNodeIds: newExpanded,
isDirty: true,
};
default:
return state;
}
};History Management
历史管理
typescript
const useHistoryManager = (
state: GraphState,
dispatch: Dispatch<GraphAction>,
) => {
const canUndo = state.historyIndex > 0;
const canRedo = state.historyIndex < state.history.length - 1;
const undo = useCallback(() => {
if (canUndo) {
const newIndex = state.historyIndex - 1;
const historyEntry = state.history[newIndex];
dispatch({
type: "RESTORE_FROM_HISTORY",
payload: {
...historyEntry,
historyIndex: newIndex,
},
});
}
}, [canUndo, state.historyIndex, state.history]);
const saveToHistory = useCallback(() => {
dispatch({ type: "SAVE_TO_HISTORY" });
}, [dispatch]);
return { canUndo, canRedo, undo, redo, saveToHistory };
};typescript
const useHistoryManager = (
state: GraphState,
dispatch: Dispatch<GraphAction>,
) => {
const canUndo = state.historyIndex > 0;
const canRedo = state.historyIndex < state.history.length - 1;
const undo = useCallback(() => {
if (canUndo) {
const newIndex = state.historyIndex - 1;
const historyEntry = state.history[newIndex];
dispatch({
type: "RESTORE_FROM_HISTORY",
payload: {
...historyEntry,
historyIndex: newIndex,
},
});
}
}, [canUndo, state.historyIndex, state.history]);
const saveToHistory = useCallback(() => {
dispatch({ type: "SAVE_TO_HISTORY" });
}, [dispatch]);
return { canUndo, canRedo, undo, redo, saveToHistory };
};Advanced Features
高级功能
Auto-Layout Integration
自动布局集成
Integrate Dagre for automatic graph layout:
typescript
import dagre from "dagre";
const layoutOptions = {
rankdir: "TB", // Top to Bottom
nodesep: 100, // Node separation
ranksep: 150, // Rank separation
marginx: 50,
marginy: 50,
edgesep: 10,
};
const applyLayout = (nodes: Node[], edges: Edge[]) => {
const g = new dagre.graphlib.Graph();
g.setGraph(layoutOptions);
g.setDefaultEdgeLabel(() => ({}));
// Add nodes to graph
nodes.forEach((node) => {
g.setNode(node.id, { width: 200, height: 100 });
});
// Add edges to graph
edges.forEach((edge) => {
g.setEdge(edge.source, edge.target);
});
// Calculate layout
dagre.layout(g);
// Apply positions
return nodes.map((node) => ({
...node,
position: {
x: g.node(node.id).x - 100,
y: g.node(node.id).y - 50,
},
}));
};
// Debounce layout calculations
const debouncedLayout = useMemo(() => debounce(applyLayout, 150), []);集成Dagre实现自动图形布局:
typescript
import dagre from "dagre";
const layoutOptions = {
rankdir: "TB", // Top to Bottom
nodesep: 100, // Node separation
ranksep: 150, // Rank separation
marginx: 50,
marginy: 50,
edgesep: 10,
};
const applyLayout = (nodes: Node[], edges: Edge[]) => {
const g = new dagre.graphlib.Graph();
g.setGraph(layoutOptions);
g.setDefaultEdgeLabel(() => ({}));
// Add nodes to graph
nodes.forEach((node) => {
g.setNode(node.id, { width: 200, height: 100 });
});
// Add edges to graph
edges.forEach((edge) => {
g.setEdge(edge.source, edge.target);
});
// Calculate layout
dagre.layout(g);
// Apply positions
return nodes.map((node) => ({
...node,
position: {
x: g.node(node.id).x - 100,
y: g.node(node.id).y - 50,
},
}));
};
// Debounce layout calculations
const debouncedLayout = useMemo(() => debounce(applyLayout, 150), []);Focus Mode
聚焦模式
Isolate selected nodes and their direct connections:
typescript
const useFocusMode = (
selectedNodeId: string,
allNodes: Node[],
allEdges: Edge[],
) => {
return useMemo(() => {
if (!selectedNodeId) return { nodes: allNodes, edges: allEdges };
// Get direct connections
const connectedNodeIds = new Set([selectedNodeId]);
const focusedEdges: Edge[] = [];
allEdges.forEach((edge) => {
if (edge.source === selectedNodeId || edge.target === selectedNodeId) {
focusedEdges.push(edge);
connectedNodeIds.add(edge.source);
connectedNodeIds.add(edge.target);
}
});
// Get connected nodes
const focusedNodes = allNodes.filter((n) => connectedNodeIds.has(n.id));
return { nodes: focusedNodes, edges: focusedEdges };
}, [selectedNodeId, allNodes, allEdges]);
};
// Smooth transitions for focus mode
const focusModeStyles = {
transition: "all 0.3s ease-in-out",
opacity: isInFocus ? 1 : 0.3,
filter: isInFocus ? "none" : "blur(2px)",
};隔离选中节点及其直接连接:
typescript
const useFocusMode = (
selectedNodeId: string,
allNodes: Node[],
allEdges: Edge[],
) => {
return useMemo(() => {
if (!selectedNodeId) return { nodes: allNodes, edges: allEdges };
// Get direct connections
const connectedNodeIds = new Set([selectedNodeId]);
const focusedEdges: Edge[] = [];
allEdges.forEach((edge) => {
if (edge.source === selectedNodeId || edge.target === selectedNodeId) {
focusedEdges.push(edge);
connectedNodeIds.add(edge.source);
connectedNodeIds.add(edge.target);
}
});
// Get connected nodes
const focusedNodes = allNodes.filter((n) => connectedNodeIds.has(n.id));
return { nodes: focusedNodes, edges: focusedEdges };
}, [selectedNodeId, allNodes, allEdges]);
};
// Smooth transitions for focus mode
const focusModeStyles = {
transition: "all 0.3s ease-in-out",
opacity: isInFocus ? 1 : 0.3,
filter: isInFocus ? "none" : "blur(2px)",
};Search Integration
搜索集成
Search and navigate to specific nodes:
typescript
const searchNodes = useCallback((nodes: Node[], query: string) => {
if (!query.trim()) return [];
const lowerQuery = query.toLowerCase();
return nodes.filter(
(node) =>
node.data.label.toLowerCase().includes(lowerQuery) ||
node.data.description?.toLowerCase().includes(lowerQuery),
);
}, []);
const navigateToSearchResult = (nodeId: string) => {
// Expand parent nodes
const nodePath = calculateBreadcrumbPath(nodeId, allNodes);
const parentIds = nodePath.slice(0, -1).map((n) => n.id);
setExpandedIds((prev) => new Set([...prev, ...parentIds]));
setSelectedNodeId(nodeId);
// Fit view to node
fitView({ nodes: [{ id: nodeId }], duration: 800 });
};搜索并导航到特定节点:
typescript
const searchNodes = useCallback((nodes: Node[], query: string) => {
if (!query.trim()) return [];
const lowerQuery = query.toLowerCase();
return nodes.filter(
(node) =>
node.data.label.toLowerCase().includes(lowerQuery) ||
node.data.description?.toLowerCase().includes(lowerQuery),
);
}, []);
const navigateToSearchResult = (nodeId: string) => {
// Expand parent nodes
const nodePath = calculateBreadcrumbPath(nodeId, allNodes);
const parentIds = nodePath.slice(0, -1).map((n) => n.id);
setExpandedIds((prev) => new Set([...prev, ...parentIds]));
setSelectedNodeId(nodeId);
// Fit view to node
fitView({ nodes: [{ id: nodeId }], duration: 800 });
};Performance Tools
性能工具
Graph Performance Analyzer
图形性能分析器
Create a performance analysis script:
javascript
// scripts/graph-analyzer.js
class GraphAnalyzer {
analyzeCode(content, filePath) {
const analysis = {
metrics: {
nodeCount: this.countNodes(content),
edgeCount: this.countEdges(content),
renderTime: this.estimateRenderTime(content),
memoryUsage: this.estimateMemoryUsage(content),
complexity: this.calculateComplexity(content),
},
issues: [],
optimizations: [],
patterns: this.detectPatterns(content),
};
// Detect performance issues
this.detectPerformanceIssues(analysis);
// Suggest optimizations
this.suggestOptimizations(analysis);
return analysis;
}
countNodes(content) {
const nodePatterns = [
/nodes:\s*\[.*?\]/gs,
/const\s+\w+\s*=\s*\[.*?id:.*?position:/gs,
];
let totalCount = 0;
nodePatterns.forEach((pattern) => {
const matches = content.match(pattern);
if (matches) {
matches.forEach((match) => {
const nodeMatches = match.match(/id:\s*['"`][^'"`]+['"`]/g);
if (nodeMatches) {
totalCount += nodeMatches.length;
}
});
}
});
return totalCount;
}
estimateRenderTime(content) {
const nodeCount = this.countNodes(content);
const edgeCount = this.countEdges(content);
// Base render time estimation (ms)
const baseTime = 5;
const nodeTime = nodeCount * 0.1;
const edgeTime = edgeCount * 0.05;
return baseTime + nodeTime + edgeTime;
}
detectPerformanceIssues(analysis) {
const { metrics } = analysis;
if (metrics.nodeCount > 500) {
analysis.issues.push({
type: "HIGH_NODE_COUNT",
severity: "high",
message: `Too many nodes (${metrics.nodeCount}). Consider virtualization.`,
suggestion: "Implement virtualization or reduce visible nodes",
});
}
if (metrics.renderTime > 16) {
analysis.issues.push({
type: "SLOW_RENDER",
severity: "high",
message: `Render time (${metrics.renderTime.toFixed(2)}ms) exceeds 60fps.`,
suggestion: "Optimize with memoization and incremental rendering",
});
}
}
}创建性能分析脚本:
javascript
// scripts/graph-analyzer.js
class GraphAnalyzer {
analyzeCode(content, filePath) {
const analysis = {
metrics: {
nodeCount: this.countNodes(content),
edgeCount: this.countEdges(content),
renderTime: this.estimateRenderTime(content),
memoryUsage: this.estimateMemoryUsage(content),
complexity: this.calculateComplexity(content),
},
issues: [],
optimizations: [],
patterns: this.detectPatterns(content),
};
// Detect performance issues
this.detectPerformanceIssues(analysis);
// Suggest optimizations
this.suggestOptimizations(analysis);
return analysis;
}
countNodes(content) {
const nodePatterns = [
/nodes:\s*\[.*?\]/gs,
/const\s+\w+\s*=\s*\[.*?id:.*?position:/gs,
];
let totalCount = 0;
nodePatterns.forEach((pattern) => {
const matches = content.match(pattern);
if (matches) {
matches.forEach((match) => {
const nodeMatches = match.match(/id:\s*['"`][^'"`]+['"`]/g);
if (nodeMatches) {
totalCount += nodeMatches.length;
}
});
}
});
return totalCount;
}
estimateRenderTime(content) {
const nodeCount = this.countNodes(content);
const edgeCount = this.countEdges(content);
// Base render time estimation (ms)
const baseTime = 5;
const nodeTime = nodeCount * 0.1;
const edgeTime = edgeCount * 0.05;
return baseTime + nodeTime + edgeTime;
}
detectPerformanceIssues(analysis) {
const { metrics } = analysis;
if (metrics.nodeCount > 500) {
analysis.issues.push({
type: "HIGH_NODE_COUNT",
severity: "high",
message: `Too many nodes (${metrics.nodeCount}). Consider virtualization.`,
suggestion: "Implement virtualization or reduce visible nodes",
});
}
if (metrics.renderTime > 16) {
analysis.issues.push({
type: "SLOW_RENDER",
severity: "high",
message: `Render time (${metrics.renderTime.toFixed(2)}ms) exceeds 60fps.`,
suggestion: "Optimize with memoization and incremental rendering",
});
}
}
}Best Practices
最佳实践
Performance Guidelines
性能指南
- Use React.memo for node components to prevent unnecessary re-renders
- Implement virtualization for graphs with 1000+ nodes
- Debounce layout calculations during rapid interactions
- Use useCallback for edge creation and manipulation functions
- Implement proper TypeScript types for nodes and edges
- 使用React.memo 包裹节点组件,避免不必要的重渲染
- 实现虚拟化 处理1000+节点的图形
- 防抖布局计算 在快速交互期间
- 使用useCallback 处理边的创建和操作函数
- 为节点和边添加合适的TypeScript类型
Memory Management
内存管理
typescript
// Use Map for O(1) lookups instead of array.find
const nodesById = useMemo(
() => new Map(allNodes.map((n) => [n.id, n])),
[allNodes],
);
// Cache layout results
const layoutCacheRef = useRef<Map<string, Node[]>>(new Map());
// Proper cleanup in useEffect
useEffect(() => {
return () => {
// Clean up any lingering references
nodesMapRef.current.clear();
edgesMapRef.current.clear();
};
}, []);typescript
// Use Map for O(1) lookups instead of array.find
const nodesById = useMemo(
() => new Map(allNodes.map((n) => [n.id, n])),
[allNodes],
);
// Cache layout results
const layoutCacheRef = useRef<Map<string, Node[]>>(new Map());
// Proper cleanup in useEffect
useEffect(() => {
return () => {
// Clean up any lingering references
nodesMapRef.current.clear();
edgesMapRef.current.clear();
};
}, []);State Optimization
状态优化
typescript
// Use useRef for objects that shouldn't trigger re-renders
const autoSaveDataRef = useRef({
nodes: [],
edges: [],
lastSaved: Date.now(),
});
// Update properties without breaking reference
const updateAutoSaveData = (newNodes: Node[], newEdges: Edge[]) => {
autoSaveDataRef.current.nodes = newNodes;
autoSaveDataRef.current.edges = newEdges;
autoSaveDataRef.current.lastSaved = Date.now();
};typescript
// Use useRef for objects that shouldn't trigger re-renders
const autoSaveDataRef = useRef({
nodes: [],
edges: [],
lastSaved: Date.now(),
});
// Update properties without breaking reference
const updateAutoSaveData = (newNodes: Node[], newEdges: Edge[]) => {
autoSaveDataRef.current.nodes = newNodes;
autoSaveDataRef.current.edges = newEdges;
autoSaveDataRef.current.lastSaved = Date.now();
};Common Problems & Solutions
常见问题与解决方案
Performance Issues
性能问题
-
Problem: Lag during node expansion
-
Solution: Implement incremental rendering with change detection
-
Problem: Memory usage increases over time
-
Solution: Proper cleanup in useEffect hooks and use WeakMap for temporary data
-
问题:节点展开时出现卡顿
-
解决方案:实现带变更检测的增量渲染
-
问题:内存使用随时间增加
-
解决方案:在useEffect中正确清理,使用WeakMap存储临时数据
Layout Conflicts
布局冲突
- Problem: Manual positioning conflicts with auto-layout
- Solution: Use controlled positioning state and separate layout modes
- 问题:手动定位与自动布局冲突
- 解决方案:使用受控定位状态,分离布局模式
Rendering Issues
渲染问题
-
Problem: Excessive re-renders
-
Solution: Use memo, useMemo, and useCallback with stable dependencies
-
Problem: Slow layout calculations
-
Solution: Debounce layout calculations and cache results
-
问题:过度重渲染
-
解决方案:使用memo、useMemo和useCallback,并确保依赖稳定
-
问题:布局计算缓慢
-
解决方案:防抖布局计算并缓存结果
Complete Example
完整示例
typescript
import React, { useState, useCallback, useMemo, useRef } from 'react';
import ReactFlow, { Node, Edge, useReactFlow } from 'reactflow';
import dagre from 'dagre';
import { debounce } from 'lodash';
interface GraphState {
nodes: Node[];
edges: Edge[];
selectedNodeId: string | null;
expandedNodeIds: Set<string>;
history: GraphState[];
historyIndex: number;
}
export default function InteractiveGraph() {
const [state, setState] = useState<GraphState>({
nodes: [],
edges: [],
selectedNodeId: null,
expandedNodeIds: new Set(),
history: [],
historyIndex: 0,
});
const { fitView } = useReactFlow();
const layoutCacheRef = useRef<Map<string, Node[]>>(new Map());
// Memoized styled edges
const styledEdges = useMemo(() => {
return state.edges.map(edge => ({
...edge,
style: {
...edge.style,
strokeWidth: state.selectedNodeId === edge.source || state.selectedNodeId === edge.target ? 3 : 2,
stroke: state.selectedNodeId === edge.source || state.selectedNodeId === edge.target ? '#3b82f6' : '#94a3b8',
},
animated: state.selectedNodeId === edge.source || state.selectedNodeId === edge.target,
}));
}, [state.edges, state.selectedNodeId]);
// Debounced layout calculation
const debouncedLayout = useMemo(
() => debounce((nodes: Node[], edges: Edge[]) => {
const cacheKey = generateLayoutCacheKey(nodes, edges);
if (layoutCacheRef.current.has(cacheKey)) {
return layoutCacheRef.current.get(cacheKey)!;
}
const layouted = applyDagreLayout(nodes, edges);
layoutCacheRef.current.set(cacheKey, layouted);
return layouted;
}, 150),
[]
);
const handleNodeClick = useCallback((event: React.MouseEvent, node: Node) => {
setState(prev => ({
...prev,
selectedNodeId: node.id,
}));
}, []);
const handleToggleExpand = useCallback((nodeId: string) => {
setState(prev => {
const newExpanded = new Set(prev.expandedNodeIds);
if (newExpanded.has(nodeId)) {
newExpanded.delete(nodeId);
} else {
newExpanded.add(nodeId);
}
return {
...prev,
expandedNodeIds: newExpanded,
};
});
}, []);
return (
<ReactFlow
nodes={state.nodes}
edges={styledEdges}
onNodeClick={handleNodeClick}
fitView
/>
);
}This comprehensive skill provides everything needed to build production-ready ReactFlow applications with hierarchical navigation, performance optimization, and advanced state management patterns.
typescript
import React, { useState, useCallback, useMemo, useRef } from 'react';
import ReactFlow, { Node, Edge, useReactFlow } from 'reactflow';
import dagre from 'dagre';
import { debounce } from 'lodash';
interface GraphState {
nodes: Node[];
edges: Edge[];
selectedNodeId: string | null;
expandedNodeIds: Set<string>;
history: GraphState[];
historyIndex: number;
}
export default function InteractiveGraph() {
const [state, setState] = useState<GraphState>({
nodes: [],
edges: [],
selectedNodeId: null,
expandedNodeIds: new Set(),
history: [],
historyIndex: 0,
});
const { fitView } = useReactFlow();
const layoutCacheRef = useRef<Map<string, Node[]>>(new Map());
// Memoized styled edges
const styledEdges = useMemo(() => {
return state.edges.map(edge => ({
...edge,
style: {
...edge.style,
strokeWidth: state.selectedNodeId === edge.source || state.selectedNodeId === edge.target ? 3 : 2,
stroke: state.selectedNodeId === edge.source || state.selectedNodeId === edge.target ? '#3b82f6' : '#94a3b8',
},
animated: state.selectedNodeId === edge.source || state.selectedNodeId === edge.target,
}));
}, [state.edges, state.selectedNodeId]);
// Debounced layout calculation
const debouncedLayout = useMemo(
() => debounce((nodes: Node[], edges: Edge[]) => {
const cacheKey = generateLayoutCacheKey(nodes, edges);
if (layoutCacheRef.current.has(cacheKey)) {
return layoutCacheRef.current.get(cacheKey)!;
}
const layouted = applyDagreLayout(nodes, edges);
layoutCacheRef.current.set(cacheKey, layouted);
return layouted;
}, 150),
[]
);
const handleNodeClick = useCallback((event: React.MouseEvent, node: Node) => {
setState(prev => ({
...prev,
selectedNodeId: node.id,
}));
}, []);
const handleToggleExpand = useCallback((nodeId: string) => {
setState(prev => {
const newExpanded = new Set(prev.expandedNodeIds);
if (newExpanded.has(nodeId)) {
newExpanded.delete(nodeId);
} else {
newExpanded.add(nodeId);
}
return {
...prev,
expandedNodeIds: newExpanded,
};
});
}, []);
return (
<ReactFlow
nodes={state.nodes}
edges={styledEdges}
onNodeClick={handleNodeClick}
fitView
/>
);
}这份全面的指南包含了构建具备层级导航、性能优化和高级状态管理模式的生产级ReactFlow应用所需的全部内容。