🏹

React Flow めっちゃいいぜ

2022/08/23に公開

はじめに

今回の記事の結論はタイトル通り『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を使ってみよう!

今回のレポジトリ

https://github.com/tkcel/react-flow-demo

環境

  • 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に渡してあげ、nodepropsで渡している配列の各要素の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』を触ってみましたが、今回ご紹介したのはほんの触りの部分なので、是非一度触ってみてください!
個人的に『フローチャートによって物事をシンプルにしたり、課題を解決できる事象』はかなり多い気がしており、本ライブラリを使って色々なサービスを作ってみたくなりました。

参考

https://reactflow.dev/

https://github.com/wbkd/react-flow

Red Frasco

Discussion