xyflow(react-flow) 入门

4,657 阅读5分钟

xyflow 是一个基于节点的编辑器和交互式图表库。通过它你可以轻松地创建可拖拽的工作流和图形界面。目前它拥有 reactsvelte 版本。感兴趣的同学可以在官网了解具体内容。

https://reactflow.dev/_next/image?url=%2Fimg%2Ffeatured%2Fdoubleloop.png&w=1920&q=75

核心概念

在学习如何使用前,你需要了解以下的概念。

节点 Nodes

在 React Flow 中,节点是一个 React 组件,这意味着它可以渲染任何你喜欢的内容。每个节点都有 x 和 y 坐标,这告诉它在视口中的位置。默认情况下,节点看起来像下面示例中的样子。当然,你还可以使用自定义节点,用于修改外观和行为,完成如渲染表单元素、添加多个句柄等等。

Untitled.png

句柄 Handles

句柄是边连接到节点的地方。句柄可以放置在任何位置,并可以按你喜欢的方式进行样式设置。它只是一个 div 元素。默认情况下,它出现在节点的顶部、底部、左侧或右侧,呈灰色圆形。在创建自定义节点时,你可以根据需要在节点中拥有尽可能多的句柄

边 Edges

边连接两个节点。每条边都需要一个目标节点和一个源节点。React Flow 内置了四种边类型:默认(贝塞尔曲线)、平滑步进、步进和直线。边是一个 SVG 路径,可以用 CSS 进行样式设置,并且完全可定制。同样的,你可以使用自定义边,来完成一些如添加移除按钮、自定义路由等行为。

Untitled 1.png

连接线 Connection Line

React Flow 内置了从一个句柄点击并拖动到另一个句柄以创建新边的功能。在拖动时,占位符边称为连接线。连接线也有四种内置类型,并且是可定制的。

视图 Viewport

所有的 flow 都存在于视图当中。视口有x、y和缩放值。并支持平移、缩放等功能。

Untitled 2.png

应用

首先需要安装相应的包,这里选择用 react 版本作为演示。

npm install @xyflow/react

一个流程由节点(nodes)和边(edges)组成(或者仅有节点)。你可以将nodesedges数组作为属性(props)传递给 ReactFlow 组件。所有节点和边的 ID 都需要是唯一的。

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

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

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} edges={edges}>
        <Background />
        <Controls />
      </ReactFlow>
    </div>
  );
}

export default Flow;

Node API

例举 Node 相关的一些核心 API,更多的可以查看官方文档

名称类型描述
idstring唯一标识
positionXYPosition对应位置坐标
dataT数据集
type?NodeType节点类型,支持 default, input, output, group

需要注意的是,width 和 height 有 Flow 内部计算,不用直接设置。

Edge API

名称类型描述
idstring唯一标识
typestring类型包括 default, straight,step,smoothstep,simplebezier
sourcestring来源 id
targetstring目标 id
sourceNode?Node
targetNode?Node
dataT

交互事件

组件支持 onNodesChangeonEdgesChange 事件,当节点或边变更的时候触发

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

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

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

function Flow() {
  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)),
    [],
  );

  return (
    <div style={{ height: '100%' }}>
      <ReactFlow
        nodes={nodes}
        onNodesChange={onNodesChange}
        edges={edges}
        onEdgesChange={onEdgesChange}
        fitView
      >
        <Background />
        <Controls />
      </ReactFlow>
    </div>
  );
}

export default Flow;

以及处理连接事件 onConnect 。当尝试通过从一个句柄拖动到另一个句柄来连接这两个节点的时候触发。

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

进阶

下面的案例来介绍一下你如何定制 Nodes 和 Edges。

自定义节点

在自定义节点中,你可以渲染任何你想要的内容。你可以定义多个源和目标句柄,并渲染表单输入或图表等。本节中,我们将实现一个带有输入字段的节点,该字段可以更新应用程序另一个部分的文本。

自定义节点是一个 React 组件,它被包装以提供基本功能,如选择或拖动。我们从包装组件中传递 props,比如位置或数据等。让我们开始实现 TextUpdaterNode。我们使用 Handle 组件使我们的自定义节点能够与其他节点连接,并在节点中添加一个输入字段。

import { useCallback } from 'react';
import { Handle, Position } from '@xyflow/react';
 
const handleStyle = { left: 10 };
 
function TextUpdaterNode({ data }) {
  const onChange = useCallback((evt) => {
    console.log(evt.target.value);
  }, []);
 
  return (
    <>
      <Handle type="target" position={Position.Top} />
      <div>
        <label htmlFor="text">Text:</label>
        <input id="text" name="text" onChange={onChange} className="nodrag" />
      </div>
      <Handle type="source" position={Position.Bottom} id="a" />
      <Handle
        type="source"
        position={Position.Bottom}
        id="b"
        style={handleStyle}
      />
    </>
  );
}

如此,你可以通过 nodeTypes 来向 React Flow 添加新的节点

const nodeTypes = useMemo(() => ({ textUpdater: TextUpdaterNode }), []);
 
const nodes = [
  {
    id: 'node-1',
    type: 'textUpdater',
    position: { x: 0, y: 0 },
    data: { value: 123 },
  },
];
return <ReactFlow nodeTypes={nodeTypes} />;

Untitled 3.png

自定义边

在 React Flow 中通过 <BaseEdge > 组件渲染,并且路径基于 SVG。Flow 中内置相关工具函数来计算要渲染的实际 SVG 路径。如 getBezierPathgetStraightPath 等。

import {
  BaseEdge,
  EdgeLabelRenderer,
  getStraightPath,
  useReactFlow,
} from '@xyflow/react';
 
export default function CustomEdge({ id, sourceX, sourceY, targetX, targetY }) {
  const { setEdges } = useReactFlow();
  const [edgePath] = getStraightPath({
    sourceX,
    sourceY,
    targetX,
    targetY,
  });
 
  return (
    <>
      <BaseEdge id={id} path={edgePath} />
      <EdgeLabelRenderer>
        <button
          onClick={() => setEdges((edges) => edges.filter((e) => e.id !== id))}
        >
          delete
        </button>
      </EdgeLabelRenderer>
    </>
  );
}

之后你可以通过定义 edgeTypes,并将 edge type 设置成对应 custom-edge 即可

import ReactFlow from '@xyflow/react'
import CustomEdge from './CustomEdge'
 
 
const edgeTypes = {
  'custom-edge': CustomEdge
}
 
export function Flow() {
  return <ReactFlow edgeTypes={edgeTypes} ... />
}

Untitled 4.png

其他能力

同时 Flow 有很多内置的插件如 <Minimap /><Controls /><Panel /> 等。以及布局渲染上可以采用三方库来支持不同形态的展示。这里就不具体展开了。