react-flow-code-review

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

React Flow Code Review

React Flow代码审查

Critical Anti-Patterns

关键反模式

1. Defining nodeTypes/edgeTypes Inside Components

1. 在组件内部定义nodeTypes/edgeTypes

Problem: Causes all nodes to re-mount on every render.
tsx
// BAD - recreates object every render
function Flow() {
  const nodeTypes = { custom: CustomNode };  // WRONG
  return <ReactFlow nodeTypes={nodeTypes} />;
}

// GOOD - defined outside component
const nodeTypes = { custom: CustomNode };
function Flow() {
  return <ReactFlow nodeTypes={nodeTypes} />;
}

// GOOD - useMemo if dynamic
function Flow() {
  const nodeTypes = useMemo(() => ({ custom: CustomNode }), []);
  return <ReactFlow nodeTypes={nodeTypes} />;
}
问题:导致所有节点在每次渲染时重新挂载。
tsx
// 错误示例 - 每次渲染都会重新创建对象
function Flow() {
  const nodeTypes = { custom: CustomNode };  // 错误写法
  return <ReactFlow nodeTypes={nodeTypes} />;
}

// 正确示例 - 在组件外部定义
const nodeTypes = { custom: CustomNode };
function Flow() {
  return <ReactFlow nodeTypes={nodeTypes} />;
}

// 正确示例 - 动态场景下使用useMemo
function Flow() {
  const nodeTypes = useMemo(() => ({ custom: CustomNode }), []);
  return <ReactFlow nodeTypes={nodeTypes} />;
}

2. Missing memo() on Custom Nodes/Edges

2. 自定义节点/Edges未使用memo()包裹

Problem: Custom components re-render on every parent update.
tsx
// BAD - no memoization
function CustomNode({ data }: NodeProps) {
  return <div>{data.label}</div>;
}

// GOOD - wrapped in memo
import { memo } from 'react';
const CustomNode = memo(function CustomNode({ data }: NodeProps) {
  return <div>{data.label}</div>;
});
问题:自定义组件会在父组件每次更新时重新渲染。
tsx
// 错误示例 - 未使用记忆化
function CustomNode({ data }: NodeProps) {
  return <div>{data.label}</div>;
}

// 正确示例 - 使用memo包裹
import { memo } from 'react';
const CustomNode = memo(function CustomNode({ data }: NodeProps) {
  return <div>{data.label}</div>;
});

3. Inline Callbacks Without useCallback

3. 内联回调未使用useCallback

Problem: Creates new function references, breaking memoization.
tsx
// BAD - inline callback
<ReactFlow
  onNodesChange={(changes) => setNodes(applyNodeChanges(changes, nodes))}
/>

// GOOD - memoized callback
const onNodesChange = useCallback(
  (changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
  []
);
<ReactFlow onNodesChange={onNodesChange} />
问题:创建新的函数引用,破坏记忆化效果。
tsx
// 错误示例 - 内联回调
<ReactFlow
  onNodesChange={(changes) => setNodes(applyNodeChanges(changes, nodes))}
/>

// 正确示例 - 记忆化回调
const onNodesChange = useCallback(
  (changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
  []
);
<ReactFlow onNodesChange={onNodesChange} />

4. Using useReactFlow Outside Provider

4. 在Provider外部使用useReactFlow

tsx
// BAD - will throw error
function App() {
  const { getNodes } = useReactFlow();  // ERROR: No provider
  return <ReactFlow ... />;
}

// GOOD - wrap in provider
function FlowContent() {
  const { getNodes } = useReactFlow();  // Works
  return <ReactFlow ... />;
}

function App() {
  return (
    <ReactFlowProvider>
      <FlowContent />
    </ReactFlowProvider>
  );
}
tsx
// 错误示例 - 会抛出错误
function App() {
  const { getNodes } = useReactFlow();  // 错误:无Provider包裹
  return <ReactFlow ... />;
}

// 正确示例 - 用Provider包裹
function FlowContent() {
  const { getNodes } = useReactFlow();  // 正常工作
  return <ReactFlow ... />;
}

function App() {
  return (
    <ReactFlowProvider>
      <FlowContent />
    </ReactFlowProvider>
  );
}

5. Storing Complex Objects in Node Data

5. 在Node Data中存储复杂对象

Problem: Reference equality checks fail, causing unnecessary updates.
tsx
// BAD - new object reference every time
setNodes(nodes.map(n => ({
  ...n,
  data: { ...n.data, config: { nested: 'value' } }  // New object each time
})));

// GOOD - use updateNodeData for targeted updates
const { updateNodeData } = useReactFlow();
updateNodeData(nodeId, { config: { nested: 'value' } });
问题:引用相等性检查失败,导致不必要的更新。
tsx
// 错误示例 - 每次都创建新对象引用
setNodes(nodes.map(n => ({
  ...n,
  data: { ...n.data, config: { nested: 'value' } }  // 每次生成新对象
})));

// 正确示例 - 使用updateNodeData进行针对性更新
const { updateNodeData } = useReactFlow();
updateNodeData(nodeId, { config: { nested: 'value' } });

Performance Checklist

性能检查清单

Node Rendering

节点渲染

  • Custom nodes wrapped in
    memo()
  • nodeTypes defined outside component or memoized
  • Heavy computations inside nodes use
    useMemo
  • Event handlers use
    useCallback
  • 自定义节点已用
    memo()
    包裹
  • nodeTypes定义在组件外部或已记忆化
  • 节点内的重计算逻辑使用
    useMemo
  • 事件处理函数使用
    useCallback

Edge Rendering

连线渲染

  • Custom edges wrapped in
    memo()
  • edgeTypes defined outside component or memoized
  • Edge path calculations are not duplicated
  • 自定义连线已用
    memo()
    包裹
  • edgeTypes定义在组件外部或已记忆化
  • 连线路径计算未重复执行

State Updates

状态更新

  • Using functional form of setState:
    setNodes((nds) => ...)
  • Not spreading entire state for single property updates
  • Using
    updateNodeData
    for data-only changes
  • Batch updates when adding multiple nodes/edges
  • 使用setState的函数式写法:
    setNodes((nds) => ...)
  • 不会为单个属性更新而展开整个状态
  • 仅更新数据时使用
    updateNodeData
  • 添加多个节点/连线时批量更新

Viewport

视口

  • Not calling
    fitView()
    on every render
  • Using
    fitViewOptions
    for initial fit only
  • Animation durations are reasonable (< 500ms)
  • 不会在每次渲染时调用
    fitView()
  • 仅在初始适配时使用
    fitViewOptions
  • 动画时长合理(< 500ms)

Common Mistakes

常见错误

Missing Container Height

缺少容器高度

tsx
// BAD - no height, flow won't render
<ReactFlow nodes={nodes} edges={edges} />

// GOOD - explicit dimensions
<div style={{ width: '100%', height: '100vh' }}>
  <ReactFlow nodes={nodes} edges={edges} />
</div>
tsx
// 错误示例 - 无高度,Flow无法渲染
<ReactFlow nodes={nodes} edges={edges} />

// 正确示例 - 显式设置尺寸
<div style={{ width: '100%', height: '100vh' }}>
  <ReactFlow nodes={nodes} edges={edges} />
</div>

Missing CSS Import

缺少CSS导入

tsx
// Required for default styles
import '@xyflow/react/dist/style.css';
tsx
// 需要导入默认样式
import '@xyflow/react/dist/style.css';

Forgetting nodrag on Interactive Elements

未在交互元素上添加nodrag

tsx
// BAD - clicking button drags node
<button onClick={handleClick}>Click</button>

// GOOD - prevents drag
<button className="nodrag" onClick={handleClick}>Click</button>
tsx
// 错误示例 - 点击按钮会拖动节点
<button onClick={handleClick}>点击</button>

// 正确示例 - 阻止拖动
<button className="nodrag" onClick={handleClick}>点击</button>

Not Using Position Constants

未使用Position常量

tsx
// BAD - string literals
<Handle type="source" position="right" />

// GOOD - type-safe constants
import { Position } from '@xyflow/react';
<Handle type="source" position={Position.Right} />
tsx
// 错误示例 - 使用字符串字面量
<Handle type="source" position="right" />

// 正确示例 - 类型安全的常量
import { Position } from '@xyflow/react';
<Handle type="source" position={Position.Right} />

Mutating Nodes/Edges Directly

直接修改Nodes/Edges

tsx
// BAD - direct mutation
nodes[0].position = { x: 100, y: 100 };
setNodes(nodes);

// GOOD - immutable update
setNodes(nodes.map(n =>
  n.id === '1' ? { ...n, position: { x: 100, y: 100 } } : n
));
tsx
// 错误示例 - 直接修改
nodes[0].position = { x: 100, y: 100 };
setNodes(nodes);

// 正确示例 - 不可变更新
setNodes(nodes.map(n =>
  n.id === '1' ? { ...n, position: { x: 100, y: 100 } } : n
));

TypeScript Issues

TypeScript问题

Missing Generic Types

缺少泛型类型

tsx
// BAD - loses type safety
const [nodes, setNodes] = useNodesState(initialNodes);

// GOOD - explicit types
type MyNode = Node<{ value: number }, 'custom'>;
const [nodes, setNodes] = useNodesState<MyNode>(initialNodes);
tsx
// 错误示例 - 失去类型安全
const [nodes, setNodes] = useNodesState(initialNodes);

// 正确示例 - 显式声明类型
type MyNode = Node<{ value: number }, 'custom'>;
const [nodes, setNodes] = useNodesState<MyNode>(initialNodes);

Wrong Props Type

错误的Props类型

tsx
// BAD - using wrong type
function CustomNode(props: any) { ... }

// GOOD - correct props type
function CustomNode(props: NodeProps<MyNode>) { ... }
tsx
// 错误示例 - 使用错误类型
function CustomNode(props: any) { ... }

// 正确示例 - 使用正确的Props类型
function CustomNode(props: NodeProps<MyNode>) { ... }

Review Questions

审查问题清单

  1. Are all custom components memoized?
  2. Are nodeTypes/edgeTypes defined outside render?
  3. Are callbacks wrapped in useCallback?
  4. Is the container sized properly?
  5. Are styles imported?
  6. Is useReactFlow used inside a provider?
  7. Are interactive elements marked with nodrag?
  8. Are types used consistently throughout?
  1. 所有自定义组件都已记忆化处理?
  2. nodeTypes/edgeTypes是否定义在渲染外部?
  3. 回调函数是否已用useCallback包裹?
  4. 容器尺寸是否设置正确?
  5. 是否已导入样式文件?
  6. useReactFlow是否在Provider内部使用?
  7. 交互元素是否已标记nodrag?
  8. 类型是否在整个项目中保持一致?