はじめに
データの流れやワークフローを視覚的に表現したい、ノードベースのエディタを作りたい。そういうニーズ、最近増えてきていると思うんですよ。
GitHubで34,000スター以上、週間270万ダウンロード。React Flowはノードベースのインタラクティブなダイアグラムを構築するためのReactコンポーネントとして、かなりの地位を確立しています。
正直なところ、最初は「フローチャートのライブラリなんて自分には関係ない」と思っていたんです。でも実際に触ってみたら、ワークフロービルダーからAIパイプライン設計まで、応用範囲の広さに驚きました。30代になって思うのは、「使わないだろう」と決めつけずに触ってみることの大切さですね。
とはいえ、説明を読むよりも実際に手を動かした方が早いと思います。ノードをドラッグしたり、ハンドル同士をつないで接続を作ったりできるサンプルを用意したので、先に挙動を確かめたい方はこちらからどうぞ。
React Flowとは
React Flowは、ノードベースのエディタやインタラクティブなダイアグラムを構築するためのカスタマイズ可能なReactコンポーネントライブラリです。
xyflow社が開発・メンテナンスしており、npmでは@xyflow/reactパッケージとして提供されています。MITライセンスのオープンソースなので、商用利用も問題なし。
Stripe、Zapier、Retool、Railwayなど、名だたるテック企業が採用しているという事実が、その信頼性を物語っています。
特徴・メリット
1. 箱から出してすぐ使える
ドラッグ、ズーム、パンニング、複数選択、要素の追加・削除。これらの機能が最初から組み込まれています。
個人的には、この「すぐ動く」感覚が一番ありがたい。ゼロから実装すると膨大な時間がかかる機能が、最初から揃っているわけです。
2. カスタマイズ性が高い
ノードは単なるReactコンポーネント。つまり、普段使っているReactの知識がそのまま活かせます。
const CustomNode = ({ data }) => {
return (
<div className="custom-node">
<Handle type="target" position={Position.Top} />
<div>{data.label}</div>
<Handle type="source" position={Position.Bottom} />
</div>
)
}
TailwindやCSS-in-JSとの相性も良好。デザインの自由度が高いので、プロジェクトの雰囲気に合わせられます。
3. パフォーマンスが考慮されている
変更されたノードだけが再レンダリングされる設計。数百ノードを扱うような場面でも、スムーズに動作します。
コスパ的に、パフォーマンス最適化を自分でやらなくていいのは大きい。
4. プラグインが充実
- Background: グリッドやドットのバックグラウンド
- MiniMap: 全体を俯瞰できるミニマップ
- Controls: ズームイン・アウトのコントロール
- Panel: フローティングパネル
- NodeToolbar: ノード操作用のツールバー
- NodeResizer: ノードのサイズ変更
これ、意外と自分で作ると大変なんですよ。標準で用意されているのは時短になる。
5. TypeScriptフレンドリー
型定義がしっかりしているので、TypeScriptで書くときのストレスがない。エディタの補完も効くし、型安全に開発できます。
インストール方法
前提条件
React 17以上のプロジェクトが必要です。
インストール
npm install @xyflow/react
以前はreactflowというパッケージ名でしたが、現在は@xyflow/reactに統一されています。
スタイルの読み込み
デフォルトスタイルを使う場合:
import '@xyflow/react/dist/style.css'
必要最小限のスタイルだけ使いたい場合:
import '@xyflow/react/dist/base.css'
React Flowのサンプルを動かす
React Flowの正体は、nodesとedgesという2つの配列を<ReactFlow />コンポーネントに渡すだけの、拍子抜けするほどシンプルなAPIです。ノードのドラッグやハンドル同士の接続は、useNodesState・useEdgesStateという2つのhookとonConnectコールバックを組み合わせるだけで動くようになります。
要点だけを抜き出すと、こんな形になります。
import { ReactFlow, addEdge, useNodesState, useEdgesState } from '@xyflow/react'
const initialNodes = [
{ id: '1', position: { x: 0, y: 0 }, data: { label: '入力' } },
{ id: '2', position: { x: 0, y: 120 }, data: { label: '処理' } },
]
const initialEdges = [{ id: 'e1-2', source: '1', target: '2' }]
function Flow() {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes)
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges)
const onConnect = (c) => setEdges((eds) => addEdge(c, eds))
return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
fitView
/>
)
}
実際に動かせるものが下です。ノードをドラッグして配置を変えたり、ノード下部の丸いハンドルから線を伸ばして別のノードに接続したりできます。左上の「ノードを追加」ボタンを押すと、setNodesで新しいノードをその場で増やせることも確認してみてください。
ドラッグで動かしたノードの位置はonNodesChangeが検知してnodesステートに反映され、ハンドル同士をつなぐとonConnect経由でaddEdgeが新しいエッジをedgesステートに追加します。この後の「基本的な使い方」「実践的なユースケース」では、この土台をベースにカスタムノードやレイアウトのパターンを見ていきます。
基本的な使い方
シンプルなフロー
import { useCallback } from 'react'
import {
ReactFlow,
addEdge,
useNodesState,
useEdgesState,
type Node,
type Edge,
type Connection,
} from '@xyflow/react'
import '@xyflow/react/dist/style.css'
// 初期ノード
const initialNodes: Node[] = [
{
id: '1',
position: { x: 0, y: 0 },
data: { label: '入力' },
type: 'input',
},
{
id: '2',
position: { x: 0, y: 100 },
data: { label: '処理' },
},
{
id: '3',
position: { x: 0, y: 200 },
data: { label: '出力' },
type: 'output',
},
]
// 初期エッジ(ノード間の接続線)
const initialEdges: Edge[] = [
{ id: 'e1-2', source: '1', target: '2' },
{ id: 'e2-3', source: '2', target: '3' },
]
function Flow() {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes)
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges)
const onConnect = useCallback(
(connection: Connection) => {
setEdges((eds) => addEdge(connection, eds))
},
[setEdges]
)
return (
<div style={{ width: '100%', height: '500px' }}>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
fitView
/>
</div>
)
}
export default Flow
これだけでドラッグ可能なノード、接続可能なエッジ、ズーム・パンが動作します。普通のhookと同じ感覚で使えるのがいいですね。
カスタムノードの作成
import { Handle, Position, type NodeProps } from '@xyflow/react'
type TaskNodeData = {
label: string
status: 'pending' | 'running' | 'completed'
}
function TaskNode({ data }: NodeProps<TaskNodeData>) {
const statusColors = {
pending: 'bg-gray-200',
running: 'bg-yellow-200',
completed: 'bg-green-200',
}
return (
<div className={`px-4 py-2 rounded-lg shadow-md ${statusColors[data.status]}`}>
<Handle type="target" position={Position.Top} />
<div className="font-medium">{data.label}</div>
<div className="text-sm text-gray-600">{data.status}</div>
<Handle type="source" position={Position.Bottom} />
</div>
)
}
// 使用時
const nodeTypes = { task: TaskNode }
function Flow() {
// ...
return (
<ReactFlow
nodes={nodes}
edges={edges}
nodeTypes={nodeTypes}
// ...
/>
)
}
Reactコンポーネントとして実装できるので、stateやpropsも普通に使える。学習コストが低いのがありがたい。
プラグインの活用
import {
ReactFlow,
Background,
Controls,
MiniMap,
} from '@xyflow/react'
function Flow() {
return (
<div style={{ width: '100%', height: '500px' }}>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
fitView
>
<Background variant="dots" gap={12} size={1} />
<Controls />
<MiniMap />
</ReactFlow>
</div>
)
}
これでグリッド背景、ズームコントロール、ミニマップが追加される。コンポーネントを追加するだけで機能が増えるのは直感的。
実践的なユースケース
ワークフロービルダー
n8nやZapierのような自動化ツールのインターフェース。トリガー→アクション→条件分岐といったフローを視覚的に組み立てられます。
const workflowNodes: Node[] = [
{
id: 'trigger',
type: 'trigger',
position: { x: 250, y: 0 },
data: { label: 'Webhook受信', icon: 'webhook' },
},
{
id: 'filter',
type: 'condition',
position: { x: 250, y: 100 },
data: { label: '条件分岐', condition: 'status === 200' },
},
{
id: 'action1',
type: 'action',
position: { x: 100, y: 200 },
data: { label: 'Slack通知' },
},
{
id: 'action2',
type: 'action',
position: { x: 400, y: 200 },
data: { label: 'DBに保存' },
},
]
上記のworkflowNodesをそのままReactFlowに渡すと、トリガーから条件分岐、2つのアクションへと分かれるフローが実際に動きます。以下のサンプルでは、ノードをドラッグして配置を変えたり、ハンドル同士をつないで新しい接続を追加したりできます。dataに持たせたkindの値でノードの背景色を出し分けている点も、React Flowのノードが単なるReactの描画対象であることの表れです。
条件分岐ノードから伸びる「YES」「NO」ラベル付きのエッジは、edges配列のlabelプロパティで指定しています。ノード同士を新しくドラッグ接続するとonConnectが呼ばれ、addEdgeによってその接続がedgesステートに追加される様子もあわせて確認できます。
マインドマップ
アイデア整理やブレインストーミング用のツール。
import { MarkerType } from '@xyflow/react'
const mindMapEdges: Edge[] = [
{
id: 'e1-2',
source: 'main',
target: 'idea1',
type: 'smoothstep',
markerEnd: { type: MarkerType.ArrowClosed },
},
// ...
]
中心のアイデアから枝を伸ばしていくマインドマップは、type: 'smoothstep'のエッジとmarkerEndのMarkerType.ArrowClosedを組み合わせると、矢印付きの曲線で一気にそれらしくなります。以下のサンプルでは、中心のmainノードから伸びる矢印付きの接続をドラッグで新規作成できるほか、「アイデアを追加」ボタンでノードとエッジを同時に増やせます。
「アイデアを追加」を押すたびにmainノードから矢印付きのエッジが伸びていきます。onConnectの中でも同じtypeとmarkerEndを指定しているので、ユーザーが手動でドラッグ接続した線も、ボタンで自動追加した線と同じ見た目に揃う点に注目してみてください。
AIパイプライン設計
LangChainやFlowise的な、AIモデルの処理フローを設計するツール。入力→プロンプトテンプレート→LLM→出力パーサーといった流れを視覚化できます。
const pipelineNodes: Node[] = [
{ id: 'input', position: { x: 0, y: 0 }, data: { label: '入力' } },
{ id: 'prompt', position: { x: 0, y: 100 }, data: { label: 'プロンプトテンプレート' } },
{ id: 'llm', position: { x: 0, y: 200 }, data: { label: 'LLM' } },
{ id: 'output', position: { x: 0, y: 300 }, data: { label: '出力パーサー' } },
]
const pipelineEdges: Edge[] = [
{ id: 'e1', source: 'input', target: 'prompt', animated: true },
{ id: 'e2', source: 'prompt', target: 'llm', animated: true },
{ id: 'e3', source: 'llm', target: 'output', animated: true },
]
入力からプロンプトテンプレート、LLM、出力パーサーへと処理が流れていく様子は、エッジにanimated: trueを指定するだけで線の上をドットが流れるように表現できます。以下のサンプルでは「実行」ボタンを押すと、data.labelが示す各ステップを順番にハイライトしながらパイプラインの実行過程を疑似的に確認できます。ノード自体はドラッグで自由に並べ替え可能です。
ノードのstyle.backgroundをステートに応じて書き換えることで、どのステップが実行中かをその場でハイライトできます。これはノードが単なるReactコンポーネントとして描画されている、React Flowならではの使い方です。
組織図・ER図
階層構造やエンティティ間の関係を表現。dagre等のレイアウトアルゴリズムと組み合わせると、自動配置も可能。
import dagre from 'dagre'
const getLayoutedElements = (nodes: Node[], edges: Edge[]) => {
const dagreGraph = new dagre.graphlib.Graph()
dagreGraph.setDefaultEdgeLabel(() => ({}))
dagreGraph.setGraph({ rankdir: 'TB' })
nodes.forEach((node) => {
dagreGraph.setNode(node.id, { width: 150, height: 50 })
})
edges.forEach((edge) => {
dagreGraph.setEdge(edge.source, edge.target)
})
dagre.layout(dagreGraph)
const layoutedNodes = nodes.map((node) => {
const nodeWithPosition = dagreGraph.node(node.id)
return {
...node,
position: {
x: nodeWithPosition.x - 75,
y: nodeWithPosition.y - 25,
},
}
})
return { nodes: layoutedNodes, edges }
}
getLayoutedElementsのようにdagre.graphlib.Graphへ各ノードの幅・高さとedgesの接続情報を登録すると、dagre.layout()が階層に応じた座標を自動計算してくれます。以下のサンプルでは、バラバラに配置した組織図のノードを「自動整列」ボタンで整列させられます。整列した後もノードはドラッグで自由に動かせます。
rankdirを'LR'から'TB'(上から下)に変えると、横方向の組織図が縦方向のツリーに切り替わります。nodesep・ranksepの値を大きくすると、ノード間の間隔が広がる様子も確認してみてください。
注意点
親要素のサイズ指定
ReactFlowは親要素の100%を占めるので、親要素に明示的なサイズが必要です。
// NG: 高さが0になる
<div>
<ReactFlow />
</div>
// OK: 高さを指定
<div style={{ height: '500px' }}>
<ReactFlow />
</div>
これ、最初にハマるポイントなので覚えておくといいです。
状態管理の設計
ノードやエッジの状態が複雑になってきたら、Zustandなど外部の状態管理と組み合わせることを検討してください。React Flowが提供するhooksと相性がいいです。
まとめ
React Flowを使ってみて感じたこと:
- 学習コスト: Reactの知識がそのまま活きる。1日で基本は把握できる
- カスタマイズ性: ノードもエッジも自由に設計可能
- パフォーマンス: 大量ノードでもスムーズ
- エコシステム: プラグイン、サンプル、ドキュメントが充実
- 実績: Stripe、Zapierなど大手が採用
正直なところ、ノードベースのUIを1から実装するのは相当な工数がかかります。ドラッグ、ズーム、接続線の描画、当たり判定...考えるだけで気が遠くなる。それがReact Flowなら、数時間で動くプロトタイプが作れる。
ワークフロービルダー、ノーコードツール、データパイプラインの可視化。こういったプロジェクトを検討しているなら、React Flow一択ですね。
まだ触ったことがない人は、公式サイトのExamplesから試してみてください。50以上のサンプルがあるので、イメージが湧きやすいと思います。