Open6

react-flow備忘録

プログラマぽい何かを名乗る生物プログラマぽい何かを名乗る生物

概念整理とかなんとか

重要コンポーネント

ReactFlow

簡単な例

  • nodesには複数のnodeを配列の中にオブジェクトを配置して渡す

nodes

dataは'label'で表示する文字列を指定できる

positionで画面のどの位置に設定するか指定できる

typeは下記の通り。

  • "default"
  • "input"
  • "output"
  • "group"

が、オーバーライド可能。

edges

const edges = [{ id: '1-2', source: '1', target: '2' }];

デフォルトだと下記のようになる。

const edges = [
  { id: '1-2', source: '1', target: '2', label: 'to the', type: 'step' },
];

typeを変更できる。

import { ReactFlow, Controls, Background } from '@xyflow/react';
import '@xyflow/react/dist/style.css';

const nodes = [
  {
    id: '1',
    data: { label: 'Hello' },
    position: { x: 0, y: 0 },
    type: 'input',
  },
  {
    id: '2',
    data: { label: 'World' },
    position: { x: 100, y: 100 },
  },
];

function Flow() {
  return (
    <div style={{ height: '100%' }}>
      <ReactFlow nodes={nodes}>
        <Background />
        <Controls />
      </ReactFlow>
    </div>
  );
}

export default Flow;

TODO:nodeタイプのオーバーライド方法

プログラマぽい何かを名乗る生物プログラマぽい何かを名乗る生物

ReactFlowコンポーネントの重要な引数

  • onNodesChange
  • onEdgesChange

使い方

import { useState, useCallback } from 'react';
import { ReactFlow, applyEdgeChanges, applyNodeChanges } from '@xyflow/react';

いつもの初期化(initialNodes,initialEdgesは任意で配列・オブジェクトの構造で渡す)

const [nodes, setNodes] = useState(initialNodes);
const [edges, setEdges] = useState(initialEdges);

const onNodesChange = useCallback(
  (changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
  [],
);
const onEdgesChange = useCallback(
  (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),
  [],
);
  1. setNodes, setEdges関数をimportする
  2. useCallbackでsetNodes,setEdgesの引数にapply〇〇Changesを渡す
  3. 関数内で定義することが前提
  4. ReactFlowコンポーネントに渡す
  5. 多分おすすめはまんまonNodesChange, onEdgesChangeで定義しちゃうことかも。
<ReactFlow
        nodes={nodes}
        onNodesChange={onNodesChange}
        edges={edges}
        onEdgesChange={onEdgesChange}
        fitView
      >
        <Background />
        <Controls />
      </ReactFlow>

公式の説明を引用すると

you’ll be able to click and drag the components, and the UI will update based on those movements.

つまり、onNodesChange, onEdgesChangeの上記書き方をすることでドラッグ可能となる。

プログラマぽい何かを名乗る生物プログラマぽい何かを名乗る生物

ReactFlowコンポーネントの重要な引数 その2

onConnect

使い方としてはこう。
つまり、useCallback内部でsetEdgesの実行時にaddEdgeを呼び出す。
多分わかりやすいのはonConnectという関数で作ってそのままReactFlowコンポーネント引数に渡すこと。

import { useState, useCallback } from 'react';
import {
  ReactFlow,
  Controls,
  Background,
  applyNodeChanges,
  applyEdgeChanges,
  addEdge,
} from '@xyflow/react';

const onConnect = useCallback(
    (params) => setEdges((eds) => addEdge(params, eds)),
    [],
  );

<ReactFlow
        nodes={nodes}
        onNodesChange={onNodesChange}
        edges={edges}
        onEdgesChange={onEdgesChange}
        onConnect={onConnect}
        fitView
      >
        <Background />
        <Controls />
      </ReactFlow>
    </div>
プログラマぽい何かを名乗る生物プログラマぽい何かを名乗る生物

Custom Nodesについて

custom nodeを定義する際、Handleコンポーネントを利用する。

https://reactflow.dev/api-reference/components/handle

The <Handle /> component is used in your custom nodes to define connection points.

接合点とでも訳そうか。
ノードから生える点を作れる。

import { Handle, Position } from '@xyflow/react';
 
export CustomNode = ({ data }) => {
  return (
    <>
      <div style={{ padding: '10px 20px' }}>
        {data.label}
      </div>
 
      <Handle type="target" position={Position.Left} />
      <Handle type="source" position={Position.Right} />
    </>
  );
};

使い方を見てみる。

import { useCallback, useState } from 'react';
import {
  ReactFlow,
  addEdge,
  applyEdgeChanges,
  applyNodeChanges,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';

import TextUpdaterNode from './TextUpdaterNode';

const rfStyle = {
  backgroundColor: '#B8CEFF',
};

const initialNodes = [
  {
    id: 'node-1',
    type: 'textUpdater',
    position: { x: 0, y: 0 },
    data: { value: 123 },
  },
];
// we define the nodeTypes outside of the component to prevent re-renderings
// you could also use useMemo inside the component
const nodeTypes = { textUpdater: TextUpdaterNode };

function Flow() {
  const [nodes, setNodes] = useState(initialNodes);
  const [edges, setEdges] = useState([]);

  const onNodesChange = useCallback(
    (changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
    [setNodes],
  );
  const onEdgesChange = useCallback(
    (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),
    [setEdges],
  );
  const onConnect = useCallback(
    (connection) => setEdges((eds) => addEdge(connection, eds)),
    [setEdges],
  );

  return (
    <ReactFlow
      nodes={nodes}
      edges={edges}
      onNodesChange={onNodesChange}
      onEdgesChange={onEdgesChange}
      onConnect={onConnect}
      nodeTypes={nodeTypes}
      fitView
      style={rfStyle}
    />
  );
}

export default Flow;

// we define the nodeTypes outside of the component to prevent re-renderings
// you could also use useMemo inside the component

とある。つまり関数の外でnodeTypesを定義する必要がある。
関数内でやる場合はuseMemoで書きましょうという話になる。

書き方はこう。

const nodeTypes = useMemo(() => ({ textUpdater: TextUpdaterNode }), []);
プログラマぽい何かを名乗る生物プログラマぽい何かを名乗る生物

意味的に小さくてフラグメントなメモ

テーマはここからコントロールできるらしい。
https://reactflow.dev/learn/customization/theming

ノードの中にノードを詰めるならこれが良さげ。

export const initialNodes = [
  {
    id: 'A',
    type: 'group',
    data: { label: null },
    position: { x: 0, y: 0 },
    style: {
      width: 170,
      height: 140,
    },
  },
  {
    id: 'B',
    type: 'input',
    data: { label: 'child node 1' },
    position: { x: 10, y: 10 },
    parentId: 'A',
    extent: 'parent',
  },
  {
    id: 'C',
    data: { label: 'child node 2' },
    position: { x: 10, y: 90 },
    parentId: 'A',
    extent: 'parent',
  },
];

つまり、extent: 'parent' と書いた上で parentId を指定すれば、ノード同士で親子関係が作成できる。

他いい感じにやる方法はこちら。

https://reactflow.dev/learn/layouting/sub-flows#:~:text=Open in Codesandbox-,Using a Default Node Type as a Parent,-Let’s remove the