mcpField
R
react-flow-architecture

react-flow-architecture

github.com/existential-birds開発85530 回インストール11 週間前に更新
リポジトリ

react-flow-architecture について

react-flow-architectureは、React Flowを使うアプリの設計を支援する開発スキルです。ノードベースのエディターやワークフローを対象に、操作、規模、状態管理、代替案、アーキテクチャの検討を導きます。

主な機能

  1. 01操作をReact Flowのコールバックに対応付け
  2. 02ノード数に応じた方針選択
  3. 03ローカル状態と外部状態の比較
  4. 04入れ子ノードとビューポート永続化の設計
  5. 05代替案と描画上のトレードオフの確認

活用シーン

  1. 01ビジュアルワークフロービルダーの設計
  2. 02フローエディターの状態管理方式の選択
  3. 03グラフ拡大時の描画方針の検討

インストール

コマンドをコピーしてターミナルで実行します。

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

インストール後、Claude Code で/react-flow-architectureを入力して読み込まれたか確認します。

詳細

React Flow のアーキテクチャ

React Flow を使用する場面

適している用途
  • ビジュアルプログラミングインターフェース
  • ワークフロービルダーと自動化ツール
  • ダイアグラムエディター(フローチャート、組織図)
  • データパイプラインの可視化
  • マインドマッピングツール
  • ノードベースのオーディオ/ビデオエディター
  • デシジョンツリービルダー
  • ステートマシン設計ツール
他の選択肢を検討するケース
  • シンプルな静的ダイアグラム(SVG または canvas を直接使用します)
  • 大規模なリアルタイムコラボレーション(カスタムの同期レイヤーが必要になる場合があります)
  • 3D ビジュアライゼーション(Three.js、react-three-fiber を使用します)
  • 1 万を超えるノードのグラフ分析(Sigma.js など、WebGL ベースのソリューションを使用します)
判断の手順(ゲート)

スタックを確定したり、実装に着手したりする前に、次の手順を実行します。使い捨てのプロトタイプの場合のみ省略できます。

  1. 操作を明確にする — 主なユーザー操作(例:ドラッグ、接続、削除、グループ化)をリストアップします。**合格条件:**各操作が、実装する具体的な React Flow のコールバック(onNodesChange、onConnect、…)に対応しています。

  2. 規模を分類する — ピーク時のノード数(キャンバスに表示される数またはドキュメント全体の数)を見積もります。**合格条件:**見積もり範囲がノード数のガイドラインのいずれかの行に当てはまり、その行に記載された方針(該当する場合は onlyRenderVisibleElements など)を受け入れています。

  3. 状態の置き場所を決める — ローカルフック、外部ストア、Redux などから選択します。**合格条件:**永続化、取り消し、または画面間の同期をどこで処理するかを一文で示すか、「まだ必要ありません」と明示しています。

  4. 他の選択肢を再確認する — ユースケースが他の選択肢を検討するケースに該当する場合、**合格条件:**React Flow が引き続き適している理由、または代わりに選んだ選択肢を一文で説明しています。

アーキテクチャパターン

パッケージ構成(xyflow)
@xyflow/system (vanilla TypeScript)
├── Core algorithms (edge paths, bounds, viewport)
├── xypanzoom (d3-based pan/zoom)
├── xydrag, xyhandle, xyminimap, xyresizer
└── Shared types

@xyflow/react (depends on @xyflow/system)
├── React components and hooks
├── Zustand store for state management
└── Framework-specific integrations

@xyflow/svelte (depends on @xyflow/system)
└── Svelte components and stores

意味すること:コアロジックはフレームワークに依存しません。コントリビュートやデバッグを行う際は、問題が @xyflow/system にあるのか、フレームワーク固有のパッケージにあるのかを確認してください。

状態管理の方法
1. ローカル状態(シンプルなアプリ)
// useNodesState/useEdgesState for prototyping
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);

メリット:シンプルで、定型コードが最小限です
デメリット:状態がコンポーネントツリー内に限定されます

2. 外部ストア(本番環境)
// Zustand store example
import { create } from 'zustand';

interface FlowStore {
  nodes: Node[];
  edges: Edge[];
  setNodes: (nodes: Node[]) => void;
  onNodesChange: OnNodesChange;
}

const useFlowStore = create<FlowStore>((set, get) => ({
  nodes: initialNodes,
  edges: initialEdges,
  setNodes: (nodes) => set({ nodes }),
  onNodesChange: (changes) => {
    set({ nodes: applyNodeChanges(changes, get().nodes) });
  },
}));

// In component
function Flow() {
  const { nodes, edges, onNodesChange } = useFlowStore();
  return <ReactFlow nodes={nodes} onNodesChange={onNodesChange} />;
}

メリット:どこからでも状態にアクセスでき、永続化や同期が容易です
デメリット:セットアップが増え、セレクターの最適化に注意が必要です

3. Redux/その他の状態管理ライブラリ
// Connect via selectors
const nodes = useSelector(selectNodes);
const dispatch = useDispatch();

const onNodesChange = useCallback((changes: NodeChange[]) => {
  dispatch(nodesChanged(changes));
}, [dispatch]);
データフローのアーキテクチャ
User Input → Change Event → Reducer/Handler → State Update → Re-render
     ↓
[Drag node] → onNodesChange → applyNodeChanges → setNodes → ReactFlow
     ↓
[Connect]   → onConnect → addEdge → setEdges → ReactFlow
     ↓
[Delete]    → onNodesDelete → deleteElements → setNodes/setEdges → ReactFlow
サブフローパターン(ネストされたノード)
// Parent node containing child nodes
const nodes = [
  {
    id: 'group-1',
    type: 'group',
    position: { x: 0, y: 0 },
    style: { width: 300, height: 200 },
  },
  {
    id: 'child-1',
    parentId: 'group-1',  // Key: parent reference
    extent: 'parent',      // Key: constrain to parent
    position: { x: 10, y: 30 },  // Relative to parent
    data: { label: 'Child' },
  },
];

考慮事項:

  • extent: 'parent' を使用してドラッグを制限します
  • expandParent: true を使用して親を自動的に拡張します
  • 親の z-index が子の描画順に影響します
ビューポートの永続化
// Save viewport state
const { toObject, setViewport } = useReactFlow();

const handleSave = () => {
  const flow = toObject();
  // flow.nodes, flow.edges, flow.viewport
  localStorage.setItem('flow', JSON.stringify(flow));
};

const handleRestore = () => {
  const flow = JSON.parse(localStorage.getItem('flow'));
  setNodes(flow.nodes);
  setEdges(flow.edges);
  setViewport(flow.viewport);
};

統合パターン

バックエンド/API との連携
// Load from API
useEffect(() => {
  fetch('/api/flow')
    .then(r => r.json())
    .then(({ nodes, edges }) => {
      setNodes(nodes);
      setEdges(edges);
    });
}, []);

// Debounced auto-save
const debouncedSave = useMemo(
  () => debounce((nodes, edges) => {
    fetch('/api/flow', {
      method: 'POST',
      body: JSON.stringify({ nodes, edges }),
    });
  }, 1000),
  []
);

useEffect(() => {
  debouncedSave(nodes, edges);
}, [nodes, edges]);
レイアウトアルゴリズムとの連携
import dagre from 'dagre';

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

  nodes.forEach((node) => {
    g.setNode(node.id, { width: 150, height: 50 });
  });

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

  dagre.layout(g);

  return {
    nodes: nodes.map((node) => {
      const pos = g.node(node.id);
      return { ...node, position: { x: pos.x, y: pos.y } };
    }),
    edges,
  };
}

パフォーマンスのスケーリング

ノード数のガイドライン
ノード数方針
< 100デフォルト設定
100-500onlyRenderVisibleElements を有効にします
500-1000カスタムノードを簡素化し、DOM 要素を減らします
> 1000仮想化や WebGL の代替手段を検討します
最適化手法
<ReactFlow
  // Only render nodes/edges in viewport
  onlyRenderVisibleElements={true}

  // Reduce node border radius (improves intersect calculations)
  nodeExtent={[[-1000, -1000], [1000, 1000]]}

  // Disable features not needed
  elementsSelectable={false}
  panOnDrag={false}
  zoomOnScroll={false}
/>

トレードオフ

制御ありと制御なし
制御あり制御なし
定型コードが多いコードが少ない
状態を完全に制御内部状態
永続化が容易toObject() が必要
複雑なアプリに適していますプロトタイプに適しています
接続モード
Strict(デフォルト)Loose
Source → Target のみ任意のハンドル → 任意のハンドル
動作が予測しやすいより柔軟
データフローに使用ダイアグラムに使用
<ReactFlow connectionMode={ConnectionMode.Loose} />
エッジの描画
デフォルトのエッジカスタムエッジ
高速に描画より細かく制御可能
スタイル設定に制限あり任意の SVG/HTML
シンプルなユースケース複雑なラベル

コメント