Token导航 LogoToken导航TokenDH.com
效率需要联网clawhub未标认证来源可访问clear审计通过

react-flow-advancedReact flow 高级

Agent Skill

用于辅助前端页面、组件、样式和交互逻辑的开发与维护。它适合让 Agent 生成或审查 React、Next.js、Vue、Tailwind、CSS 等相关代码,整理组件结构,或定位布局和性能问题。使用时需要结合项目现有设计系统、路由和构建方式,避免只生成孤立片段;涉及页面改动时,应配合本地预览和构建检查确认视觉效果。

总安装

5,132

周安装

216

GitHub Stars

公开资料未说明

下载量

1,797
OpenClaw

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

MIT-0

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:react-flow-advanced(React flow 高级)
来源仓库:https://github.com/anderskev/react-flow-advanced
安装命令:
openclaw skills install react-flow-advanced
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 OpenClaw 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

ClawHubOpenClaw
openclaw skills install react-flow-advanced

简介

适用于复杂用例的高级 React Flow 模式。在实现子流程、自定义连接线、编程布局、拖放、撤消/重做等时使用

SKILL.md

name
react-flow-advanced
description
Advanced React Flow patterns for complex use cases. Use when implementing sub-flows, custom connection lines, programmatic layouts, drag-and-drop, undo/redo, or complex state synchronization.

Advanced React Flow Patterns

Gates (check before shipping)

Use these as sequenced checks—not “I think it works.”

  1. Sub-flows / groups: Pass: Every parentId matches an existing node id; the parent type is registered in nodeTypes; child positions are relative to the parent as intended (spot-check one drag inside/outside the group).
  2. Custom connection line: Pass: With a valid/invalid drag, stroke or connectionStatus visibly differs; path renders without console errors from getSmoothStepPath (invalid coords).
  3. External drag-and-drop: Pass: onDragOver always preventDefault(); drop position uses screenToFlowPosition (not raw clientX/clientY as flow coords); new node appears under the cursor on the pane.
  4. Undo/redo: Pass: One undo returns to the prior { nodes, edges }; redo restores; rapid changes do not leave canUndo/canRedo inconsistent with visible graph (exercise add → undo → redo once).
  5. Programmatic layout (dagre): Pass: After setNodes, node positions match intended rankdir; fitView runs after layout (e.g. requestAnimationFrame) so the viewport is not stale.
  6. Connect on drop (new node): Pass: Dropping on empty pane creates a node and an edge from the source handle; dropping on a valid target does not duplicate nodes (only the invalid-drop path adds a node).
  7. Selectors / store: Pass: Components that useStore with objects use shallow (or equivalent) so unrelated store updates do not re-render every frame.

Sub-Flows (Nested Nodes)

const nodes = [
  // Parent (group) node
  {
    id: 'group-1',
    type: 'group',
    position: { x: 0, y: 0 },
    style: { width: 400, height: 300, padding: 10 },
    data: { label: 'Group' },
  },
  // Child nodes
  {
    id: 'child-1',
    parentId: 'group-1',        // Reference parent
    extent: 'parent',           // Constrain to parent bounds
    expandParent: true,         // Auto-expand parent if dragged to edge
    position: { x: 20, y: 50 }, // Relative to parent
    data: { label: 'Child 1' },
  },
  {
    id: 'child-2',
    parentId: 'group-1',
    extent: 'parent',
    position: { x: 200, y: 50 },
    data: { label: 'Child 2' },
  },
];

Group Node Component

function GroupNode({ data, id }: NodeProps) {
  return (
    <div className="group-node">
      <div className="group-header">{data.label}</div>
      {/* Children are rendered automatically by React Flow */}
    </div>
  );
}

Custom Connection Line

import { ConnectionLineComponentProps, getSmoothStepPath } from '@xyflow/react';

function CustomConnectionLine({
  fromX, fromY, fromPosition,
  toX, toY, toPosition,
  connectionStatus,
}: ConnectionLineComponentProps) {
  const [path] = getSmoothStepPath({
    sourceX: fromX,
    sourceY: fromY,
    sourcePosition: fromPosition,
    targetX: toX,
    targetY: toY,
    targetPosition: toPosition,
  });

  return (
    <g>
      <path
        d={path}
        fill="none"
        stroke={connectionStatus === 'valid' ? '#22c55e' : '#ef4444'}
        strokeWidth={2}
        strokeDasharray="5 5"
      />
    </g>
  );
}

<ReactFlow connectionLineComponent={CustomConnectionLine} />

Drag and Drop from External Source

import { useCallback, useRef, useState } from 'react';
import { useReactFlow } from '@xyflow/react';

function DnDFlow() {
  const reactFlowWrapper = useRef(null);
  const { screenToFlowPosition, addNodes } = useReactFlow();
  const [reactFlowInstance, setReactFlowInstance] = useState(null);

  const onDragOver = useCallback((event: DragEvent) => {
    event.preventDefault();
    event.dataTransfer.dropEffect = 'move';
  }, []);

  const onDrop = useCallback((event: DragEvent) => {
    event.preventDefault();

    const type = event.dataTransfer.getData('application/reactflow');
    if (!type) return;

    // Convert screen position to flow position
    const position = screenToFlowPosition({
      x: event.clientX,
      y: event.clientY,
    });

    const newNode = {
      id: `${Date.now()}`,
      type,
      position,
      data: { label: `${type} node` },
    };

    addNodes(newNode);
  }, [screenToFlowPosition, addNodes]);

  return (
    <div ref={reactFlowWrapper} style={{ height: '100%' }}>
      <ReactFlow
        onDragOver={onDragOver}
        onDrop={onDrop}
        onInit={setReactFlowInstance}
      />
    </div>
  );
}

// Sidebar component
function Sidebar() {
  const onDragStart = (event: DragEvent, nodeType: string) => {
    event.dataTransfer.setData('application/reactflow', nodeType);
    event.dataTransfer.effectAllowed = 'move';
  };

  return (
    <aside>
      <div draggable onDragStart={(e) => onDragStart(e, 'input')}>
        Input Node
      </div>
      <div draggable onDragStart={(e) => onDragStart(e, 'default')}>
        Default Node
      </div>
    </aside>
  );
}

Undo/Redo

import { useCallback, useState } from 'react';

function useUndoRedo<T>(initialState: T) {
  const [history, setHistory] = useState<T[]>([initialState]);
  const [index, setIndex] = useState(0);

  const state = history[index];

  const setState = useCallback((newState: T | ((prev: T) => T)) => {
    setHistory((prev) => {
      const resolved = typeof newState === 'function'
        ? (newState as (prev: T) => T)(prev[index])
        : newState;

      // Remove future states and add new state
      const newHistory = prev.slice(0, index + 1);
      return [...newHistory, resolved];
    });
    setIndex((i) => i + 1);
  }, [index]);

  const undo = useCallback(() => {
    setIndex((i) => Math.max(0, i - 1));
  }, []);

  const redo = useCallback(() => {
    setIndex((i) => Math.min(history.length - 1, i + 1));
  }, [history.length]);

  const canUndo = index > 0;
  const canRedo = index < history.length - 1;

  return { state, setState, undo, redo, canUndo, canRedo };
}

// Usage
function Flow() {
  const {
    state: { nodes, edges },
    setState,
    undo, redo, canUndo, canRedo
  } = useUndoRedo({ nodes: initialNodes, edges: initialEdges });

  // Capture state on significant changes
  const onNodesChange = useCallback((changes) => {
    const hasPositionChange = changes.some(c => c.type === 'position' && !c.dragging);
    if (hasPositionChange) {
      setState(prev => ({
        nodes: applyNodeChanges(changes, prev.nodes),
        edges: prev.edges,
      }));
    }
  }, [setState]);
}

Programmatic Layout with dagre

import dagre from 'dagre';

interface LayoutOptions {
  direction: 'TB' | 'BT' | 'LR' | 'RL';
  nodeWidth: number;
  nodeHeight: number;
}

function getLayoutedElements(
  nodes: Node[],
  edges: Edge[],
  options: LayoutOptions = { direction: 'TB', nodeWidth: 172, nodeHeight: 36 }
) {
  const g = new dagre.graphlib.Graph();
  g.setGraph({ rankdir: options.direction });
  g.setDefaultEdgeLabel(() => ({}));

  nodes.forEach((node) => {
    g.setNode(node.id, {
      width: node.measured?.width ?? options.nodeWidth,
      height: node.measured?.height ?? options.nodeHeight,
    });
  });

  edges.forEach((edge) => {
    g.setEdge(edge.source, edge.target);
  });

  dagre.layout(g);

  const layoutedNodes = nodes.map((node) => {
    const nodeWithPosition = g.node(node.id);
    return {
      ...node,
      position: {
        x: nodeWithPosition.x - (node.measured?.width ?? options.nodeWidth) / 2,
        y: nodeWithPosition.y - (node.measured?.height ?? options.nodeHeight) / 2,
      },
    };
  });

  return { nodes: layoutedNodes, edges };
}

// Usage after nodes are measured
function Flow() {
  const { fitView } = useReactFlow();

  const onLayout = useCallback((direction: 'TB' | 'LR') => {
    const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements(
      nodes,
      edges,
      { direction, nodeWidth: 150, nodeHeight: 50 }
    );

    setNodes([...layoutedNodes]);
    setEdges([...layoutedEdges]);

    window.requestAnimationFrame(() => {
      fitView({ duration: 500 });
    });
  }, [nodes, edges, setNodes, setEdges, fitView]);
}

Connection with Edge on Drop

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

  const onConnectEnd = useCallback(
    (event: MouseEvent | TouchEvent, connectionState: FinalConnectionState) => {
      // Only proceed if dropped on pane (not on a node)
      if (!connectionState.isValid && connectionState.fromHandle) {
        const id = `${Date.now()}`;
        const { clientX, clientY } = 'changedTouches' in event
          ? event.changedTouches[0]
          : event;

        const newNode = {
          id,
          position: screenToFlowPosition({ x: clientX, y: clientY }),
          data: { label: 'New Node' },
        };

        setNodes((nds) => [...nds, newNode]);
        setEdges((eds) => [
          ...eds,
          {
            id: `e-${connectionState.fromNode?.id}-${id}`,
            source: connectionState.fromNode?.id ?? '',
            target: id,
          },
        ]);
      }
    },
    [screenToFlowPosition, setNodes, setEdges]
  );

  return (
    <ReactFlow
      nodes={nodes}
      edges={edges}
      onNodesChange={onNodesChange}
      onEdgesChange={onEdgesChange}
      onConnectEnd={onConnectEnd}
    />
  );
}

Accessing Node Data from Edges

import { useNodesData, type EdgeProps } from '@xyflow/react';

function DataEdge({ source, target, ...props }: EdgeProps) {
  // Get data for source and target nodes
  const nodesData = useNodesData([source, target]);
  const sourceData = nodesData[0];
  const targetData = nodesData[1];

  const [path, labelX, labelY] = getSmoothStepPath(props);

  return (
    <>
      <BaseEdge path={path} />
      <EdgeLabelRenderer>
        <div style={{ transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)` }}>
          {sourceData?.data?.label} → {targetData?.data?.label}
        </div>
      </EdgeLabelRenderer>
    </>
  );
}

Middleware for Node Changes

// Filter or modify changes before they're applied
const onNodesChangeMiddleware = useCallback((changes: NodeChange[]) => {
  // Example: Prevent deletion of certain nodes
  const filteredChanges = changes.filter((change) => {
    if (change.type === 'remove') {
      const node = nodes.find((n) => n.id === change.id);
      return node?.data?.deletable !== false;
    }
    return true;
  });

  setNodes((nds) => applyNodeChanges(filteredChanges, nds));
}, [nodes, setNodes]);

Keyboard Shortcuts

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

function Flow() {
  const { deleteElements, getNodes, getEdges, fitView } = useReactFlow();

  // Ctrl/Cmd + A: Select all
  const selectAllPressed = useKeyPress(['Meta+a', 'Control+a']);

  useEffect(() => {
    if (selectAllPressed) {
      setNodes((nds) => nds.map((n) => ({ ...n, selected: true })));
      setEdges((eds) => eds.map((e) => ({ ...e, selected: true })));
    }
  }, [selectAllPressed]);

  // Custom delete handler
  const deletePressed = useKeyPress(['Backspace', 'Delete']);

  useEffect(() => {
    if (deletePressed) {
      const selectedNodes = getNodes().filter((n) => n.selected);
      const selectedEdges = getEdges().filter((e) => e.selected);
      deleteElements({ nodes: selectedNodes, edges: selectedEdges });
    }
  }, [deletePressed]);
}

Performance: Memoizing Selectors

import { useCallback } from 'react';
import { useStore, type ReactFlowState } from '@xyflow/react';
import { shallow } from 'zustand/shallow';

// Create stable selector outside component
const nodesSelector = (state: ReactFlowState) => state.nodes;

// Or use multiple values with shallow compare
const flowStateSelector = (state: ReactFlowState) => ({
  nodes: state.nodes,
  edges: state.edges,
  viewport: state.transform,
});

function FlowInfo() {
  const { nodes, edges, viewport } = useStore(flowStateSelector, shallow);
  return <div>Nodes: {nodes.length}, Edges: {edges.length}</div>;
}

适合场景

01

OpenClaw 用户查找和安装 Skill 时

02

用户想查找某类 Agent Skill 时

03

需要根据任务场景推荐可安装能力包时

04

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

补充不同宿主或平台的使用分布数据

能力 5

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

OpenClaw

88.56%
按下载量换算1,591

安全审计

VirusTotal

通过

ClawScan

通过

Static analysis

通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills