【メモ】TypeScriptで始めるReactFlow

最近、フロントエンドも触る必要があり、ReactとTypeScriptを触り始めました。このメモではReactのコンポーネントの一つであるReact Flowをの使い方をメモしておきます。

【公式ページ】

reactflow.dev

GitHubページ】 github.com

1)新しいReactプロジェクトの作成(TypeScript対応)

$ npx create-react-app my-flow-app --template typescript
$ cd my-flow-app/

以下のように表示されればプロジェクトのテンプレートの作成に成功しています。

Success! Created my-flow-app at /home/user2404/my-flow-app
Inside that directory, you can run several commands:

作成されたディレクトリは以下のようになっています。

$ ls -al
total 704
drwxr-xr-x   5 user2404 user2404   4096 Nov  1 12:57 .
drwxr-x---  16 user2404 user2404   4096 Nov  1 12:56 ..
-rw-r--r--   1 user2404 user2404    310 Nov  1 12:57 .gitignore
-rw-r--r--   1 user2404 user2404   2117 Nov  1 12:57 README.md
drwxr-xr-x 885 user2404 user2404  36864 Nov  1 13:03 node_modules
-rw-r--r--   1 user2404 user2404 645580 Nov  1 13:03 package-lock.json
-rw-r--r--   1 user2404 user2404   1001 Nov  1 13:03 package.json
drwxr-xr-x   2 user2404 user2404   4096 Nov  1 12:57 public
drwxr-xr-x   2 user2404 user2404   4096 Nov  1 12:57 src
-rw-r--r--   1 user2404 user2404    535 Nov  1 12:57 tsconfig.json

2)パッケージのインストール

プロジェクトディレクトリができたら必要となるreactflowのパッケージをインストールします。

$ npm install reactflow

【メモ】npxコマンドnpmコマンドの違い

npxコマンドNode Package Executeの略で、Node.jsに付属するコマンドラインツールです。開発環境に多くのコマンドをインストールせず、環境を整えることができるというようなものです。

npxコマンドの主な特徴

  • パッケージをインストールせずに一時的に実行できる
  • ローカルにインストールされたパッケージを簡単に実行できる
  • インストールしないため、常に最新バージョンのパッケージを使用可能

3)ファイルの編集(追加・修正)

作成時のテンプレートディレクトリは以下の様になっています。

オリジナルのテンプレートのファイルツリー

my-flow-app/
├── src/
│   ├── App.tsx        # メインのアプリケーションコンポーネント
│   ├── App.css
│   ├── index.tsx
│   └── ...
├── package.json
└── ...

編集手順

ここから以下の作業を行います。

  1. BasicFlow.tsxの作成
  2. App.tsxの修正 … BasicFlow.tsxを取り込む
  3. App.cssの修正 … CSSの記述を追加

1.BasicFlow.tsxの作成

【my-flow-app/src/components/BasicFlow.tsx

import React, { useState, useCallback } from 'react';
import ReactFlow, {
  Node,
  Edge,
  Connection,
  addEdge,
  Background,
  Controls,
  MiniMap
} from 'reactflow';
import 'reactflow/dist/style.css';

const initialNodes: Node[] = [
  {
    id: '1',
    type: 'input',
    data: { label: 'Input Node' },
    position: { x: 250, y: 25 },
  },
  {
    id: '2',
    data: { label: 'Default Node' },
    position: { x: 100, y: 125 },
  },
  {
    id: '3',
    type: 'output',
    data: { label: 'Output Node' },
    position: { x: 250, y: 250 },
  },
];

const initialEdges: Edge[] = [
  { id: 'e1-2', source: '1', target: '2' },
  { id: 'e2-3', source: '2', target: '3' },
];

const BasicFlow = () => {
  const [nodes, setNodes] = useState<Node[]>(initialNodes);
  const [edges, setEdges] = useState<Edge[]>(initialEdges);

  const onConnect = useCallback(
    (connection: Connection) => {
      setEdges((eds) => addEdge(connection, eds));
    },
    []
  );

  return (
    <div style={{ width: '100%', height: '500px' }}>
      <ReactFlow
        nodes={nodes}
        edges={edges}
        onConnect={onConnect}
        fitView
      >
        <Background />
        <Controls />
        <MiniMap />
      </ReactFlow>
    </div>
  );
};

export default BasicFlow;

2. App.tsxの修正 … BasicFlow.tsxを取り込む

【my-flow-app/src/App.tsx

import React from 'react';
import './App.css';
// ReactFlowのCSSをインポート
import 'reactflow/dist/style.css';
//ReactFlowのコンポーネントをインポート
import BasicFlow from './components/BasicFlow';

function App() {
  return (
    <div className="App">
      <h1>React Flow Chart Demo</h1>
      {/* ReacFlowのコンポーネントを配置 */}
      <div style={{ width: '100%', height: '600px', border: '1px solid #ccc' }}>
        <BasicFlow />
      </div>
    </div>
  );
}

export default App;

3. App.cssの修正 … CSSの記述を追加

【my-flow-app/src/App.css

.App {
  text-align: center;
}
/* (追加部分)ReactFlowのスタイルをインポート */
@import 'reactflow/dist/style.css';


.App-logo {
  height: 40vmin;
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .App-logo {
    animation: App-logo-spin infinite 20s linear;
  }
}

.App-header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

.App-link {
  color: #61dafb;
}

@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

編集後のオリジナルのテンプレートのファイルツリー

my-flow-app/
├── node_modules/
├── public/
│   ├── index.html
│   ├── favicon.ico
│   └── ...
├── src/
│   ├── components/
│   │   └── BasicFlow.tsx    # (作成)ReactFlowコンポーネント
│   ├── App.tsx             # (修正)メインアプリケーションファイル
│   ├── App.css            # (修正)スタイル
│   ├── index.tsx          # エントリーポイント
│   └── ...
├── package.json
├── tsconfig.json          # TypeScript設定
└── README.md

プログラムの実行

ファイルの編集が終わったら以下のように実行します。実行パスはプロジェクトのディレクトリで行ってください。

$ npm start

上記のように起動すると以下のような表示が行われます。

そして、ブラウザでhttp://localhost:3000がタブで開きます。もし開かなかった場合にはコンソール上に表示されたURLにアクセスしても同様の結果を得る事ができます。

これで第一歩が完成しました。実行後にファイルの編集を行うと保存時に反映してくれるホットリロードの機能があるので、起動後はファイルを編集するだけで修正結果を確認出来ます。

ちょっとした説明

React Flowは以下のコンポーネントを基本として構成されています。

基本的なコンポーネント

  • Node[] … ノードの配列
  • Edge[] … エッジ(接続線)の配列
  • ReactFlow … メインコンポーネント

これらのコンポーネントを貼り付けることでグラフ構造を形作ります。Nodeはノードつまり箱のような形をしたものになり、Edgeは箱同士を結ぶリンクとなります。これらのデータのリストを使用してReact Flowはグラフを描画していきます。

Node[] コンポーネント

リストに各ノードの情報(属性)を格納していきます。基本的な属性としては以下のようなものがあります。

  • id … ユニークなID(これを使用してEdgeの起点・終点を定義します)
  • type … ノードの属性(コンポーネントの動きや色の指定などにも使ます)
  • data … 情報(コンポーネントによってデータが異なる)
  • position ... 座標値

今回の例では以下の3つのノードが存在しています。座標系に関してはReact Flowコンポーネントの左上が(0,0)となっています。

const initialNodes: Node[] = [
  {
    id: '1',
    type: 'input',
    data: { label: 'Input Node' },
    position: { x: 250, y: 25 },
  },
  {
    id: '2',
    data: { label: 'Default Node' },
    position: { x: 100, y: 125 },
  },
  {
    id: '3',
    type: 'output',
    data: { label: 'Output Node' },
    position: { x: 250, y: 250 },
  },
];

Edge[] コンポーネント

リストに各エッジの情報(属性)を格納していきます。基本的な属性としては以下のようなものがあります。 基本的な属性としては以下のようなものがあります。

  • id … ユニークなID
  • source … 起点となるNodeのID
  • target … 終点となるNodeのID

今回は以下の様になっています。特に座標値はなく、結ばれれるノードの座標情報を使用して描画が行われます。

const initialEdges: Edge[] = [
  { id: 'e1-2', source: '1', target: '2' },
  { id: 'e2-3', source: '2', target: '3' },
];

コンポーネントのステート・コールバックを設定

ステート(コンポーネントの状態)とコールバック処理を設定しています。 今回のコールバック処理ではEdgeを追加する処理になっています。

const BasicFlow = () => {
  const [nodes, setNodes] = useState<Node[]>(initialNodes);
  const [edges, setEdges] = useState<Edge[]>(initialEdges);

  const onConnect = useCallback(
    (connection: Connection) => {
      setEdges((eds) => addEdge(connection, eds));
    },
    []
  );

コンポーネントのReturn

設定したコンポーネントを返す処理をしています。先程のNodeEdgeコールバック処理(onConnect)を値として渡しています。

  return (
    <div style={{ width: '100%', height: '500px' }}>
      <ReactFlow
        nodes={nodes}
        edges={edges}
        onConnect={onConnect}
        fitView
      >
        <Background />
        <Controls />
        <MiniMap />
      </ReactFlow>
    </div>
  );

おわりに

ノードの編集機能や拡大縮小といった機能のない、もっともシンプルな形のグラフをReact Flowで作成するメモでした。

/* -----codeの行番号----- */