React Flow めっちゃいいぜ
はじめに
今回の記事の結論はタイトル通り『React Flowめっちゃいいぜ』です。
こんにちは。株式会社Red Frascoの根本(github: tkcel )と申します。普段はフロントエンドを主に担当することが多いです。
この前個人開発中に以下の壁にぶち当たりました。
- React(Next)でぐりぐりできるフローチャートみたいなものを作るにはどうすればいいのか?
- 描画系のサービスはcanvasで作る方がいいのか?DOMに落とし込む方がいいのか?
同じような悩みを持っている方がたくさんいる気がする(?)のですが、なかなか日本語検索でヒットしないので、今回記事を書こうと思いました。
React Flowとは?
まずは、公式サイトからの翻訳から。
- ノードベースのエディターとインタラクティブなダイアグラムを構築するための高度にカスタマイズ可能な React コンポーネント
- MIT ライセンスのオープン ソース ライブラリ
- React Flow には、シームレスなズームとパン、カスタマイズ可能なノードとエッジ タイプ、単一選択と複数選択、複数のイベント ハンドラーなどが付属しています。
- React Flow には、サブグラフとネストされたノードのレンダリングのサポートが組み込まれています。
- React Flow には、ReactFlow コンポーネントの外部の内部状態にアクセスするために使用できる MiniMap、Controls、Background、および FlowProvider が含まれています。
個人的に何が良かったか?
個人的には、以下の点が非常に使ってて良いなと思ったところです。
- Typescript対応なので、型安全にかなり考慮されている
- 構成要素である『Node』『Edge』の型を自由に設計でき、構造がとてもシンプル
- カスタムコンポーネントがとても作りやすい → 作りたいものを作るまでの速度が早い(個人的には、まず何から作れば良いかわからなかったので、ライブラリ自体がとても勉強になりました...!)
- ドキュメントがかなり豊富
React Flowを使ってみよう!
今回のレポジトリ
環境
- node: v16系
主なライブラリ
- Next.js
- tailwindcss
- react-flow
プロジェクトをクローンする
まずは、任意のディレクトリにレポジトリをクローンしてください。
$ git clone git@github.com:tkcel/react-flow-demo.git
ライブラリ系のインストール
# Pj Rootで
$ yarn
起動
# Pj Rootで
$ yarn dev
/editorで触ってみる
localhost:3000/editor
にアクセスし、触ってみてください。
localhost:3000/
localhost:3000/editor
ここがポイント!
NodeとEdgeの型をつける
src/pages/Editor.tsx
の以下の部分について、
const initialNodes: Node<NodeDataType>[] = [
{
id: '1',
data: {
label: 'Node 1',
name: 'Sample Event Node 1',
color: '#38B5AD',
},
position: { x: 5, y: 5 },
type: 'eventNode',
},
{
id: '2',
data: { label: 'Node 2', name: 'test2', color: '#FF5660' },
position: { x: 5, y: 100 },
},
{
id: '3',
data: { label: 'Node 3', name: 'test3', color: '#FF5660' },
position: { x: 400, y: 200 },
},
]
-
Node<NodeDataType>
この部分で、Nodeの中身(data)の型をジェネリクスで渡せる。 - 実際にプロジェクトに適用する場合、この部分をうまく自分のサービスのビジネスロジックと連携するイメージです。(僕らのサービスでは、GraphQL(NestJS)を使用しており、サーバーサイドでschemeを作ってそれを利用する形で使っています)
NodeとEdgeの型定義
export interface Node<T = any> {
id: string;
position: XYPosition;
data: T;
type?: string;
style?: CSSProperties;
className?: string;
targetPosition?: Position;
sourcePosition?: Position;
hidden?: boolean;
selected?: boolean;
dragging?: boolean;
draggable?: boolean;
selectable?: boolean;
connectable?: boolean;
dragHandle?: string;
width?: number | null;
height?: number | null;
parentNode?: string;
zIndex?: number;
extent?: 'parent' | CoordinateExtent;
expandParent?: boolean;
positionAbsolute?: XYPosition;
[internalsSymbol]?: {
z?: number;
handleBounds?: NodeHandleBounds;
isParent?: boolean;
};
}
export interface Edge<T = any> {
id: string;
type?: string;
source: string;
target: string;
sourceHandle?: string | null;
targetHandle?: string | null;
label?: string | ReactNode;
labelStyle?: CSSProperties;
labelShowBg?: boolean;
labelBgStyle?: CSSProperties;
labelBgPadding?: [number, number];
labelBgBorderRadius?: number;
style?: CSSProperties;
animated?: boolean;
hidden?: boolean;
data?: T;
className?: string;
sourceNode?: Node;
targetNode?: Node;
selected?: boolean;
markerStart?: EdgeMarkerType;
markerEnd?: EdgeMarkerType;
zIndex?: number;
}
CustomNodeを登録する
src/components/molecules/EventNode
で、カスタムノードを作っています。selected
などの状態管理用のpropsを受け取れるようになっているので、状態管理もかなり楽に行えるなという印象でした。
// モック用のNODEコンポーネント
import { NodeDataType } from '@/components/pages/Editor'
import { Handle, Position } from 'react-flow-renderer'
interface EventNodeProps {
data: NodeDataType
selected: boolean
}
export const EventNode = ({ data, selected }: EventNodeProps) => {
return (
<>
<p className="ml-1 text-[6px] text-gray-400 bg-[#f5f5f5]">{data.name}</p>
<div
className={
selected
? 'border-2 border-blue-400 p-1 relative justify-center items-center'
: 'border-2 border-transparent p-1 relative justify-center items-center'
}
>
<div
className={`rounded-full bg-white border-2 px-2 py-1 justify-center items-center`}
style={{ borderColor: data.color }}
>
<span
className="font-semibold text-center"
style={{ color: data.color }}
>
{data.name}
</span>
<Handle
type="source"
style={{ top: '50%' }}
position={Position.Right}
/>
</div>
</div>
</>
)
}
このようにCustomNodeを作成し、ReactFlow
コンポーネントのnodeTypes
に渡してあげ、node
propsで渡している配列の各要素のtype
で登録したものを利用する形で反映が可能です。
src/pages/Editor.tsx
import { EventNode } from '@/components/molecules/EventNode'
// Nodeのtypeを指定
const initialNodes: Node<NodeDataType>[] = [
{
id: '1',
data: {
label: 'Node 1',
name: 'Sample Event Node 1',
color: '#38B5AD',
},
position: { x: 5, y: 5 },
type: 'eventNode', // ここで指定
},
// ~ 省略 ~
]
// ~ 省略 ~
// 登録準備
const nodeTypes = { eventNode: EventNode }
// ~ 省略 ~
// ReactFlowで登録
<ReactFlow
nodes={nodes} // ここ!
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onEdgeUpdate={onEdgeUpdate}
nodeTypes={nodeTypes} // ここ!
onSelectionChange={onSelectionChange}
fitView
fitViewOptions={fitViewOptions}
>
<Controls />
<Background style={{ backgroundColor: '#f5f5f5' }} />
</ReactFlow>
その他
その他にも以下のような点が嬉しかったです。
- NodeやEdgeの動作イベントがかなり豊富(Interectionが豊富)
- 公式で状態を管理できるHooksが用意されている
おわりに
今回簡単に『React Flow』を触ってみましたが、今回ご紹介したのはほんの触りの部分なので、是非一度触ってみてください!
個人的に『フローチャートによって物事をシンプルにしたり、課題を解決できる事象』はかなり多い気がしており、本ライブラリを使って色々なサービスを作ってみたくなりました。
参考
Red Frascoは、不動産テックの企業です。日本と海外で培った技術力・知見・業界内での繋がりをもって、1つでも業界の課題を解消できるよう挑戦し続けています。一緒に戦ってくださる方を募集しています(red-frasco.com/recruit/)。
Discussion