mcpField
R
react-flow

react-flow

github.com/existential-birds开发851.3k 次安装11 周前更新
代码仓库

关于 react-flow

react-flow 是用于在 React 应用中构建节点图、工作流编辑器和交互式图表的库,适合开发可视化编程界面与流程图。它提供可定制节点和边、事件处理及视口控制等能力。

主要功能

  1. 01构建节点与边组成的图
  2. 02自定义节点和连接线
  3. 03管理受控图状态
  4. 04处理选择、拖动与连接事件
  5. 05控制视口及坐标转换
  6. 06支持连接校验与节点更新

适用场景

  1. 01构建可视化工作流编辑器
  2. 02呈现节点网络或流程图
  3. 03实现可交互的可视化编程界面
  4. 04按用户操作更新图结构

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/existential-birds/beagle --skill react-flow

安装后在 Claude Code 中输入/react-flow确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
custom-edges.md
custom-nodes.md
events.md
viewport.md

详细介绍

React Flow

React Flow(@xyflow/react)是一个用于构建基于节点的图、工作流编辑器和交互式图表的库。它提供了高度可定制的框架,可用于创建可视化编程界面、流程图和网络可视化。

快速开始

安装
pnpm add @xyflow/react
基本设置
import { ReactFlow, Node, Edge, Background, Controls, MiniMap } from '@xyflow/react';
import '@xyflow/react/dist/style.css';

const initialNodes: Node[] = [
  {
    id: '1',
    type: 'input',
    data: { label: 'Input Node' },
    position: { x: 250, y: 5 },
  },
  {
    id: '2',
    data: { label: 'Default Node' },
    position: { x: 100, y: 100 },
  },
  {
    id: '3',
    type: 'output',
    data: { label: 'Output Node' },
    position: { x: 400, y: 100 },
  },
];

const initialEdges: Edge[] = [
  { id: 'e1-2', source: '1', target: '2', animated: true },
  { id: 'e2-3', source: '2', target: '3' },
];

function Flow() {
  return (
    <div style={{ width: '100vw', height: '100vh' }}>
      <ReactFlow nodes={initialNodes} edges={initialEdges}>
        <Background />
        <Controls />
        <MiniMap />
      </ReactFlow>
    </div>
  );
}

export default Flow;

核心概念

节点

节点是图的构建块。每个节点包含:

  • id:唯一标识符
  • type:节点类型(内置或自定义)
  • position:{ x, y } 坐标
  • data:自定义数据对象
import { Node } from '@xyflow/react';

const node: Node = {
  id: 'node-1',
  type: 'default',
  position: { x: 100, y: 100 },
  data: { label: 'Node Label' },
  style: { background: '#D6D5E6' },
  className: 'custom-node',
};

内置节点类型:

  • default:标准节点
  • input:没有目标连接点
  • output:没有源连接点
  • group:其他节点的容器
边

边用于连接节点。每条边都需要:

  • id:唯一标识符
  • source:源节点 ID
  • target:目标节点 ID
import { Edge } from '@xyflow/react';

const edge: Edge = {
  id: 'e1-2',
  source: '1',
  target: '2',
  type: 'smoothstep',
  animated: true,
  label: 'Edge Label',
  style: { stroke: '#fff', strokeWidth: 2 },
};

内置边类型:

  • default:贝塞尔曲线
  • straight:直线
  • step:带尖角的正交线
  • smoothstep:带圆角的正交线
连接点

连接点是节点上的连接位置。使用 Position 枚举设置位置:

import { Handle, Position } from '@xyflow/react';

<Handle type="target" position={Position.Top} />
<Handle type="source" position={Position.Bottom} />

可用位置:Position.Top、Position.Right、Position.Bottom、Position.Left

状态管理

受控流程

使用状态钩子进行完全控制:

import { useNodesState, useEdgesState, addEdge, OnConnect } from '@xyflow/react';
import { useCallback } from 'react';

function ControlledFlow() {
  const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
  const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);

  const onConnect: OnConnect = useCallback(
    (connection) => setEdges((eds) => addEdge(connection, eds)),
    [setEdges]
  );

  return (
    <ReactFlow
      nodes={nodes}
      edges={edges}
      onNodesChange={onNodesChange}
      onEdgesChange={onEdgesChange}
      onConnect={onConnect}
    />
  );
}
useReactFlow 钩子

访问 React Flow 实例以进行编程控制:

import { useReactFlow } from '@xyflow/react';

function FlowControls() {
  const {
    getNodes,
    getEdges,
    setNodes,
    setEdges,
    addNodes,
    addEdges,
    deleteElements,
    fitView,
    zoomIn,
    zoomOut,
    getNode,
    getEdge,
    updateNode,
    updateEdge,
  } = useReactFlow();

  return (
    <button onClick={() => fitView()}>Fit View</button>
  );
}

自定义节点

使用带类型数据的 NodeProps<T> 定义自定义节点:

import { NodeProps, Node, Handle, Position } from '@xyflow/react';

export type CustomNode = Node<{ label: string; status: 'active' | 'inactive' }, 'custom'>;

function CustomNodeComponent({ data, selected }: NodeProps<CustomNode>) {
  return (
    <div className={`px-4 py-2 ${selected ? 'ring-2' : ''}`}>
      <Handle type="target" position={Position.Top} />
      <div className="font-bold">{data.label}</div>
      <Handle type="source" position={Position.Bottom} />
    </div>
  );
}

通过 nodeTypes 注册:

const nodeTypes: NodeTypes = { custom: CustomNodeComponent };
<ReactFlow nodeTypes={nodeTypes} />
关键模式
  • 多个连接点:使用 id 属性和 style 设置位置
  • 动态连接点:添加或移除连接点后调用 useUpdateNodeInternals([nodeId])
  • 交互元素:为输入框和按钮添加 className="nodrag",以防止拖动

有关详细模式(包括样式、航空地图图钉和动态连接点),请参阅自定义节点参考。

自定义边

使用 EdgeProps<T> 和路径工具定义自定义边:

import { BaseEdge, EdgeProps, getBezierPath } from '@xyflow/react';

export type CustomEdge = Edge<{ status: 'normal' | 'error' }, 'custom'>;

function CustomEdgeComponent(props: EdgeProps<CustomEdge>) {
  const [edgePath] = getBezierPath(props);

  return (
    <BaseEdge
      id={props.id}
      path={edgePath}
      style={{ stroke: props.data?.status === 'error' ? '#ef4444' : '#64748b' }}
    />
  );
}
路径工具
  • getBezierPath() - 平滑曲线
  • getStraightPath() - 直线
  • getSmoothStepPath() - 带圆角的正交线
  • getSmoothStepPath({ borderRadius: 0 }) - 带尖角的正交线(step 边)

这些工具都返回 [path, labelX, labelY, offsetX, offsetY]。

交互式标签

使用 EdgeLabelRenderer 创建支持指针事件的 HTML 标签:

import { EdgeLabelRenderer, BaseEdge, getBezierPath } from '@xyflow/react';

function ButtonEdge(props: EdgeProps) {
  const [edgePath, labelX, labelY] = getBezierPath(props);
  return (
    <>
      <BaseEdge id={props.id} path={edgePath} />
      <EdgeLabelRenderer>
        <div
          style={{
            position: 'absolute',
            transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`,
            pointerEvents: 'all',
          }}
          className="nodrag nopan"
        >
          <button onClick={() => console.log('Delete')}>×</button>
        </div>
      </EdgeLabelRenderer>
    </>
  );
}

有关动画边、时间标签和 SVG 文本模式,请参阅自定义边参考。

视口控制

使用 useReactFlow() 钩子以编程方式控制视口:

import { useReactFlow } from '@xyflow/react';

function ViewportControls() {
  const { fitView, zoomIn, zoomOut, setCenter, screenToFlowPosition } = useReactFlow();

  // Fit all nodes in view
  const handleFitView = () => fitView({ padding: 0.2, duration: 400 });

  // Zoom controls
  const handleZoomIn = () => zoomIn({ duration: 300 });
  const handleZoomOut = () => zoomOut({ duration: 300 });

  // Center on specific coordinates
  const handleCenter = () => setCenter(250, 250, { zoom: 1.5, duration: 500 });

  // Convert screen coordinates to flow coordinates
  const addNodeAtClick = (event: React.MouseEvent) => {
    const position = screenToFlowPosition({ x: event.clientX, y: event.clientY });
    // Use position to add node
  };

  return null;
}

有关状态保存与恢复、受控视口和坐标转换,请参阅视口参考。

事件

React Flow 提供全面的事件处理功能:

节点事件
import { NodeMouseHandler, OnNodeDrag } from '@xyflow/react';

const onNodeClick: NodeMouseHandler = (event, node) => {
  console.log('Node clicked:', node.id);
};

const onNodeDrag: OnNodeDrag = (event, node, nodes) => {
  console.log('Dragging:', node.id);
};

<ReactFlow
  onNodeClick={onNodeClick}
  onNodeDrag={onNodeDrag}
  onNodeDragStop={onNodeClick}
/>
边和连接事件
import { EdgeMouseHandler, OnConnect } from '@xyflow/react';

const onEdgeClick: EdgeMouseHandler = (event, edge) => console.log('Edge:', edge.id);
const onConnect: OnConnect = (connection) => console.log('Connected:', connection);

<ReactFlow onEdgeClick={onEdgeClick} onConnect={onConnect} />
选择和视口事件
import { useOnSelectionChange, useOnViewportChange } from '@xyflow/react';

useOnSelectionChange({
  onChange: ({ nodes, edges }) => console.log('Selected:', nodes.length, edges.length),
});

useOnViewportChange({
  onChange: (viewport) => console.log('Viewport:', viewport.zoom),
});

有关完整的事件目录(包括验证、删除和错误处理),请参阅事件参考。

常见模式

防止拖动/平移
<input className="nodrag" />
<button className="nodrag nopan">Click me</button>
连接验证
const isValidConnection = (connection: Connection) => {
  return connection.source !== connection.target; // Prevent self-connections
};

<ReactFlow isValidConnection={isValidConnection} />
点击时添加节点
const { screenToFlowPosition, setNodes } = useReactFlow();

const onPaneClick = (event: React.MouseEvent) => {
  const position = screenToFlowPosition({ x: event.clientX, y: event.clientY });
  setNodes(nodes => [...nodes, { id: `node-${Date.now()}`, position, data: { label: 'New' } }]);
};
更新节点数据
const { updateNodeData } = useReactFlow();
updateNodeData('node-1', { label: 'Updated' });
updateNodeData('node-1', (node) => ({ ...node.data, count: node.data.count + 1 }));

Provider 模式

在流程外使用 useReactFlow() 时,使用 ReactFlowProvider 包裹应用:

import { ReactFlow, ReactFlowProvider, useReactFlow } from '@xyflow/react';

function Controls() {
  const { fitView } = useReactFlow(); // Must be inside provider
  return <button onClick={() => fitView()}>Fit View</button>;
}

function App() {
  return (
    <ReactFlowProvider>
      <Controls />
      <ReactFlow nodes={nodes} edges={edges} />
    </ReactFlowProvider>
  );
}

实现检查项

在将集成视为完成之前,请按以下顺序检查(这些检查针对常见易错点,而非风格偏好)。

  1. 打包文件中的 CSS — 确保应用中执行了 import '@xyflow/react/dist/style.css'(位于入口文件或布局中)。**通过标准:**节点和边具有预期的默认样式;连接点可见且可交互。
  2. 稳定的 nodeTypes / edgeTypes — 不要在每次渲染时传入新的对象字面量;请在组件外定义映射,或使用 useMemo 和正确的依赖项进行记忆化。**通过标准:**仅选择状态或视口发生变化时,不会出现重新挂载闪烁、“maximum update depth”错误或持续更新。
  3. Provider 边界 — 调用 useReactFlow() 的组件必须是 ReactFlowProvider 的后代,并且流程必须实际挂载。**通过标准:**运行时不会出现缺少上下文的错误;程序化 API(如 fitView)可在预期位置正常工作。

参考文件

如需了解详细的实现模式,请参阅:

  • 自定义节点 - NodeProps 类型、Handle 组件、动态连接点、样式模式
  • 自定义边 - EdgeProps 类型、路径工具、EdgeLabelRenderer、动画边
  • 视口 - useReactFlow 方法、fitView 选项、坐标转换
  • 事件 - 节点/边/连接事件、选择处理、视口变化

评论

支持 Markdown