計算フロー
通常、React Flowでは、開発者はデータをサーバーやデータベースなど、別の場所に送信して、React Flowの外部でデータを処理します。代わりに、このガイドでは、React Flow内で直接データフローを計算する方法を示します。これは、接続されたデータに基づいてノードを更新したり、ブラウザー内で完全に実行されるアプリを構築したりするために使用できます。
何を作るか?
このガイドの終わりまでに、3つの別々の数値入力フィールド(赤、緑、青)から色を生成し、その背景色で白または黒のテキストのどちらが読みやすいかを判断するインタラクティブなフローグラフを構築します。
カスタムノードの作成
まず、カスタム入力ノード(NumberInput.js
)を作成し、そのインスタンスを3つ追加します。制御された<input type="number" />
を使用し、onChange
イベントハンドラー内で0〜255の整数に制限します。
import { useCallback, useState } from 'react';
import { Handle, Position } from '@xyflow/react';
function NumberInput({ id, data }) {
const [number, setNumber] = useState(0);
const onChange = useCallback((evt) => {
const cappedNumber = Math.round(
Math.min(255, Math.max(0, evt.target.value)),
);
setNumber(cappedNumber);
}, []);
return (
<div className="number-input">
<div>{data.label}</div>
<input
id={`number-${id}`}
name="number"
type="number"
min="0"
max="255"
onChange={onChange}
className="nodrag"
value={number}
/>
<Handle type="source" position={Position.Right} />
</div>
);
}
export default NumberInput;
次に、各カラーチャネルに1つのターゲットハンドルと、結果の色を表示する背景を持つ新しいカスタムノード(ColorPreview.js
)を追加します。mix-blend-mode: 'difference';
を使用して、テキストの色を常に読みやすくすることができます。
単一のノードに同じ種類のハンドルが複数ある場合は、それぞれに個別のIDを付けることを忘れないでください。
また、入力ノードからカラーノードへのエッジを、initialEdges
配列に追加しましょう。
データの計算
入力ノードからカラーノードにデータを取得するにはどうすればよいでしょうか?これは、この目的のために作成された2つのフックを含む、2段階のプロセスです。
updateNodeData
コールバックを使用して、各数値入力値をノードのdata
オブジェクト内に保存します。useHandleConnections
を使用してどのノードが接続されているかを調べ、次にuseNodesData
を使用して、接続されたノードからデータを受信します。
ステップ1:データオブジェクトへの値の書き込み
まず、initialNodes
配列のdata
オブジェクト内に入力ノードの初期値を追加し、入力ノードの初期状態として使用します。次に、useReactFlow
フックから関数updateNodeData
を取得し、入力が変更されるたびに新しい値でノードのdata
オブジェクトを更新するために使用します。
デフォルトでは、updateNodeData
に渡すデータは、古いデータオブジェクトとマージされます。これにより、部分的な更新が容易になり、{...data}
の追加を忘れた場合に役立ちます。代わりにオブジェクトを置き換えるオプションとして、{ replace: true }
を渡すことができます。
入力フィールドを扱う場合、ノードのdata
オブジェクトをUI状態として直接使用することは避けてください。
データオブジェクトの更新に遅延が発生し、カーソルが不規則にジャンプして、不要な入力につながる可能性があります。
ステップ2:接続されたノードからのデータの取得
まず、useHandleConnections
フックを使用して各ハンドルのすべての接続を特定し、updateNodeData
を使用して、最初に接続されたノードのデータを取得します。
各ハンドルには複数のノードを接続できることに注意してください。アプリケーション内で単一のハンドルへの接続数を制限することができます。その方法については、接続制限の例をご覧ください。
これで完了です!入力値を変更して、色がリアルタイムで変化するのを確認してください。
コードの改善
最初にコネクションを取得し、その後各ハンドルに対して個別にデータを取得するのは不自然に思えるかもしれません。このような複数のハンドルを持つノードの場合、接続状態とノードのデータバインディングを分離するカスタムハンドルコンポーネントを作成することを検討する必要があります。インラインで作成できます。
// {...}
function CustomHandle({ id, label, onChange }) {
const connections = useHandleConnections({
type: 'target',
id,
});
const nodeData = useNodesData(connections?.[0].source);
useEffect(() => {
onChange(nodeData?.data ? nodeData.data.value : 0);
}, [nodeData]);
return (
<div>
<Handle
type="target"
position={Position.Left}
id={id}
className="handle"
/>
<label htmlFor="red" className="label">
{label}
</label>
</div>
);
}
色をローカルステートに昇格させ、各ハンドルをこのように宣言できます。
// {...}
function ColorPreview() {
const [color, setColor] = useState({ r: 0, g: 0, b: 0 });
return (
<div
className="node"
style={{
background: `rgb(${color.r}, ${color.g}, ${color.b})`,
}}
>
<CustomHandle
id="red"
label="R"
onChange={(value) => setColor((c) => ({ ...c, r: value }))}
/>
<CustomHandle
id="green"
label="G"
onChange={(value) => setColor((c) => ({ ...c, g: value }))}
/>
<CustomHandle
id="blue"
label="B"
onChange={(value) => setColor((c) => ({ ...c, b: value }))}
/>
</div>
);
}
export default ColorPreview;
より複雑にする
これで、React Flow を通してデータをパイプする方法の簡単な例ができました。では、途中でデータを変換したり、異なるパスを通ったりするなど、もっと複雑なことをしたい場合はどうすればよいでしょうか?それも可能です!
フローを継続する
フローを拡張してみましょう。まず、カラーノードに <Handle type="source" position={Position.Right} />
という出力ハンドルを追加し、ローカルコンポーネントのステートを削除します。
このノードには入力フィールドがないため、ローカルステートを保持する必要はまったくありません。ノードの data
オブジェクトを直接読み書きできます。
次に、カラーオブジェクトを入力として受け取り、それが明るい色か暗い色かを判断する新しいノード(Lightness.js
)を追加します。色の知覚される明るさ(0が最も暗く、255が最も明るい)を計算するために、相対輝度式 luminance = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b
を使用できます。128以上のものはすべて明るい色であると仮定できます。
条件分岐
知覚される明るさに基づいてフローで異なるパスを取りたい場合はどうすればよいでしょうか?明るさノードに2つのソースハンドル light
と dark
を与え、ノードの data
オブジェクトをソースハンドルIDごとに分割します。複数のソースハンドルがある場合に、各ソースハンドルのデータを区別するために必要です。
しかし、「異なるルートを取る」とはどういう意味でしょうか?1つの解決策としては、ターゲットハンドルに接続された null
または undefined
のデータは「停止」とみなすと仮定することです。この場合、明るい色であれば data.values.light
に、暗い色であれば data.values.dark
に、それぞれ入力された色を書き込み、もう一方の値を null
に設定することができます。
flex-direction: column;
と align-items: end;
を追加して、ハンドルラベルの位置を変更することを忘れないでください。
できました!これで、実際に動作するかどうかを確認するための最後のノードが必要なだけです。フックアップされたデータを表示するカスタムデバッグノード(Log.js
)を作成すれば、完了です!
まとめ
フローを通してデータを移動し、途中で変換する方法を学びました。必要なことは以下のとおりです。
updateNodeData
コールバックの助けを借りて、ノードのdata
オブジェクト内にデータを保存します。useHandleConnections
を使用して接続されているノードを見つけ出し、次に接続されたノードからデータを受け取るためにuseNodesData
を使用します。
例えば、undefined である入力データを「停止」として解釈することで、分岐を実装できます。余談ですが、分岐もあるほとんどのフローグラフでは、ノードのトリガーとノードにフックアップされた実際のデータは通常分離されています。Unreal Engine のブループリントが良い例です。
最後に、始める前に注意すべきことがもう1つあります。今やったようにアイデアを混在させるのではなく、すべてのノードデータを構造化する一貫した方法を見つける必要があります。例えば、ハンドルIDごとにデータを分割する作業を開始する場合、複数のハンドルを持っているかどうかに関係なく、すべてのノードでそれを行う必要があります。フロー全体でデータの構造について仮定できると、作業が非常に楽になります。