react-flow-architecture について
react-flow-architectureは、React Flowを使うアプリの設計を支援する開発スキルです。ノードベースのエディターやワークフローを対象に、操作、規模、状態管理、代替案、アーキテクチャの検討を導きます。
主な機能
- 01操作をReact Flowのコールバックに対応付け
- 02ノード数に応じた方針選択
- 03ローカル状態と外部状態の比較
- 04入れ子ノードとビューポート永続化の設計
- 05代替案と描画上のトレードオフの確認
活用シーン
- 01ビジュアルワークフロービルダーの設計
- 02フローエディターの状態管理方式の選択
- 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 ベースのソリューションを使用します)
判断の手順(ゲート)
スタックを確定したり、実装に着手したりする前に、次の手順を実行します。使い捨てのプロトタイプの場合のみ省略できます。
-
操作を明確にする — 主なユーザー操作(例:ドラッグ、接続、削除、グループ化)をリストアップします。**合格条件:**各操作が、実装する具体的な React Flow のコールバック(
onNodesChange、onConnect、…)に対応しています。 -
規模を分類する — ピーク時のノード数(キャンバスに表示される数またはドキュメント全体の数)を見積もります。**合格条件:**見積もり範囲がノード数のガイドラインのいずれかの行に当てはまり、その行に記載された方針(該当する場合は
onlyRenderVisibleElementsなど)を受け入れています。 -
状態の置き場所を決める — ローカルフック、外部ストア、Redux などから選択します。**合格条件:**永続化、取り消し、または画面間の同期をどこで処理するかを一文で示すか、「まだ必要ありません」と明示しています。
-
他の選択肢を再確認する — ユースケースが他の選択肢を検討するケースに該当する場合、**合格条件:**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-500 | onlyRenderVisibleElements を有効にします |
| 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 |
| シンプルなユースケース | 複雑なラベル |