搭配 TypeScript 使用
React Flow 是以 TypeScript 編寫的,因為我們重視它提供的額外安全屏障。我們匯出所有您需要的類型,以便正確地為傳遞給 React Flow 元件的資料結構和函式設定類型。我們還提供了一種擴充節點和邊緣類型的方法。
基本用法
讓我們從您簡單起點所需的最基本類型開始。Typescript 可能已經推斷出其中一些類型,但我們仍然會明確定義它們。
import { useState, useCallback } from 'react';
import {
ReactFlow,
addEdge,
applyNodeChanges,
applyEdgeChanges,
type Node,
type Edge,
type FitViewOptions,
type OnConnect,
type OnNodesChange,
type OnEdgesChange,
type OnNodeDrag,
type NodeTypes,
type DefaultEdgeOptions,
} from '@xyflow/react';
const initialNodes: Node[] = [
{ id: '1', data: { label: 'Node 1' }, position: { x: 5, y: 5 } },
{ id: '2', data: { label: 'Node 2' }, position: { x: 5, y: 100 } },
];
const initialEdges: Edge[] = [{ id: 'e1-2', source: '1', target: '2' }];
const fitViewOptions: FitViewOptions = {
padding: 0.2,
};
const defaultEdgeOptions: DefaultEdgeOptions = {
animated: true,
};
const nodeTypes: NodeTypes = {
num: NumberNode,
txt: TextNode,
};
const onNodeDrag: OnNodeDrag = (_, node) => {
console.log('drag event', node.data);
};
function Flow() {
const [nodes, setNodes] = useState<Node[]>(initialNodes);
const [edges, setEdges] = useState<Edge[]>(initialEdges);
const onNodesChange: OnNodesChange = useCallback(
(changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
[setNodes],
);
const onEdgesChange: OnEdgesChange = useCallback(
(changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),
[setEdges],
);
const onConnect: OnConnect = useCallback(
(connection) => setEdges((eds) => addEdge(connection, eds)),
[setEdges],
);
return (
<ReactFlow
nodes={nodes}
nodeTypes={nodeTypes}
edges={edges}
edgeTypes={edgeTypes}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onNodeDrag={onNodeDrag}
fitView
fitViewOptions={fitViewOptions}
defaultEdgeOptions={defaultEdgeOptions}
/>
);
}
自訂節點
當使用自訂節點時,您可以將自訂的 Node
類型(或您的 Node
聯集)傳遞給 NodeProps
類型。基本上有兩種使用自訂節點的方式
- 如果您有多個自訂節點,您想要將特定的
Node
類型作為泛型傳遞給NodeProps
類型
import type { Node, NodeProps } from '@xyflow/react';
type NumberNode = Node<{ number: number }, 'number'>;
export default function NumberNode({ data }: NodeProps<NumberNode>) {
return <div>A special number: {data.number}</div>;
}
⚠️ 如果您單獨指定節點資料,您需要使用 type
(interface
在這裡不起作用)
type NumberNodeData = { number: number };
type NumberNode = Node<NumberNodeData, 'number'>;
- 如果您有一個自訂節點,會根據節點類型呈現不同的內容,您想要將您的
Node
聯集類型作為泛型傳遞給NodeProps
import type { Node, NodeProps } from '@xyflow/react';
type NumberNode = Node<{ number: number }, 'number'>;
type TextNode = Node<{ text: string }, 'text'>;
type AppNode = NumberNode | TextNode;
export default function CustomNode({ data }: NodeProps<AppNode>) {
if (data.type === 'number') {
return <div>A special number: {data.number}</div>;
}
return <div>A special text: {data.text}</div>;
}
自訂邊緣
對於自訂邊緣,您擁有與自訂節點相同的可能性。
import {
getStraightPath,
BaseEdge,
type EdgeProps,
type Edge,
} from '@xyflow/react';
type CustomEdge = Edge<{ value: number }, 'custom'>;
export default function CustomEdge({
id,
sourceX,
sourceY,
targetX,
targetY,
}: EdgeProps<CustomEdge>) {
const [edgePath] = getStraightPath({ sourceX, sourceY, targetX, targetY });
return <BaseEdge id={id} path={edgePath} />;
}
進階用法
當使用 React Flow 建立複雜應用程式時,您將會擁有多個自訂節點和邊緣,每個節點和邊緣都附加了不同種類的資料。當我們透過內建函式和鉤子操作這些節點和邊緣時,我們必須確保我們縮小節點和邊緣的類型,以防止執行階段錯誤。
Node
和 Edge
類型聯集
您會看到許多函式、回呼和鉤子(甚至是 ReactFlow 元件本身)都期望有 NodeType
或 EdgeType
泛型。這些泛型只是您應用程式中所有不同節點和邊緣類型的聯集。只要您已正確設定資料物件的類型(請參閱上一節),您就可以使用它們匯出的類型。
如果您使用任何內建節點('input'、'output'、'default')或邊緣('straight'、'step'、'smoothstep'、'bezier'),您可以將從 @xyflow/react
匯出的 BuiltInNode
和 BuiltInEdge
類型新增至您的聯集類型。
import type { BuiltInNode, BuiltInEdge } from '@xyflow/react';
// Custom nodes
import NumberNode from './NumberNode';
import TextNode from './TextNode';
// Custom edge
import EditableEdge from './EditableEdge';
export type CustomNodeType = BuiltInNode | NumberNode | TextNode;
export type CustomEdgeType = BuiltInEdge | EditableEdge;
傳遞至 <ReactFlow />
的函式
若要接收回呼函式的正確類型,您可以將您的聯集類型傳遞給 ReactFlow
元件。這樣做,您必須明確設定回呼函式的類型。
import { type OnNodeDrag } from '@xyflow/react';
// ...
// Pass your union type here ...
const onNodeDrag: OnNodeDrag<CustomNodeType> = useCallback((_, node) => {
if (node.type === 'number') {
// From here on, Typescript knows that node.data
// is of type { num: number }
console.log('drag event', node.data.number);
}
}, []);
const onNodesChange: OnNodesChange<CustomNodeType> = useCallback(
(changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
[setNodes],
);
鉤子
類型聯集也可以用於設定許多鉤子的傳回值類型。
import {
useReactFlow,
useHandleConnections,
useNodesData,
useStore,
} from '@xyflow/react';
export default function FlowComponent() {
// returned nodes and edges are correctly typed now
const { getNodes, getEdges } = useReactFlow<CustomNodeType, CustomEdgeType>();
// You can type useStore by typing the selector function
const nodes = useStore((s: ReactFlowState<CustomNodeType>) => ({
nodes: s.nodes,
}));
const connections = useHandleConnections({
type: 'target',
});
const nodesData = useNodesData<CustomNodeType>(connections?.[0].source);
nodeData.forEach(({ type, data }) => {
if (type === 'number') {
// This is type safe because we have narrowed down the type
console.log(data.number);
}
});
// ...
}
類型保護
您可以使用多種方式在 Typescript 中定義類型保護。一種方式是定義類型保護函式,例如 isNumberNode
或 isTextNode
,以從節點清單中篩選出特定節點。
function isNumberNode(node: CustomNodeType): node is NumberNode {
return node.type === 'number';
}
// numberNodes is of type NumberNode[]
const numberNodes = nodes.filter(isNumberNode);