React Flow 绘制可拖拽流程图

4,076 阅读1分钟

React hooks 项目中使用React Flow实现拖拽流程图

  • 最近项目需求需要实现一个自定义流程图
  • 查了下资料 加上大神推荐 最终选用React Flow
  • 最开始写demo的时候是9.5的版本 后来官方api升级到10.0 用法改变了很多 参考着api修改 升级后性能提升很多

image.png

安装

npm install react-flow-renderer

使用

ReactFlow Api

按照官方demo 实现拖拽功能

GIF.gif index.tsx

const ReactFlowDemo: React.FC<{}> = () => {
  const reactFlowWrapper = useRef<HTMLDivElement>(null);
  const [nodes, setNodes, onNodesChange] = useNodesState([]);
  const [edges, setEdges, onEdgesChange] = useEdgesState([]);
  const [reactFlowInstance, setReactFlowInstance] = useState(null);


  const onConnect = useCallback((params) => setEdges((eds) => addEdge({ ...params, markerEnd: { type: MarkerType.Arrow } }, eds)), []);
  const onDragOver = useCallback((event) => {
    event.preventDefault();
    event.dataTransfer.dropEffect = 'move';
  }, []);
  const onDrop = (event: React.DragEvent) => {
    event.preventDefault();
    const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect();
    const type = event?.dataTransfer.getData('application/reactflow');

    if (typeof type === 'undefined' || !type) {
      return;
    }
    const position = reactFlowInstance.project({
      x: event.clientX - reactFlowBounds.left,
      y: event.clientY - reactFlowBounds.top,
    });
    let newNode = {
      id: getId(),
      type: type,
      position,
      data: { label: `${type} node` },
    };
    setNodes((es) => es.concat(newNode));
  }

  const onNodeClick = (event: React.MouseEvent, node: Node) => {
    let { data, id } = node
    console.log("element>>", node)
  };
  useEffect(() => {
  }, [])
  return (
    <PageHeaderWrapper >
      <div className={styles.main}>
        <div className={styles.dndflow}>
          <ReactFlowProvider >
            <Sidebar />
            <div className={styles.reactflowWrapper} ref={reactFlowWrapper}>
              <ReactFlow
                nodes={nodes}
                edges={edges}
                onNodesChange={onNodesChange}
                onEdgesChange={onEdgesChange}
                onNodeClick={onNodeClick}
                onConnect={onConnect}
                onInit={setReactFlowInstance}
                onDrop={onDrop}
                onDragOver={onDragOver}
                defaultZoom={1.0} minZoom={1} maxZoom={1.5}
              >
                <Controls />
              </ReactFlow>
            </div>
          </ReactFlowProvider>
        </div>
      </div>

    </PageHeaderWrapper>
  )
}

Sidebar.tsx

const Sidebar: React.FC<flowSiderProps> = (props) => {
  // 获取画布上的节点
  const onDragStart = (evt: React.DragEvent, nodeType: string) => {
    // 记录被拖拽的节点类型
    evt.dataTransfer.setData('application/reactflow', nodeType);
    evt.dataTransfer.effectAllowed = 'move';
  };

  return (
    <aside>
      <div className="dndnode input" onDragStart={(event) => onDragStart(event, 'input')} draggable>
        Input Node
      </div>
      <div className="dndnode" onDragStart={(event) => onDragStart(event, 'default')} draggable>
        Default Node
      </div>
      <div className="dndnode output" onDragStart={(event) => onDragStart(event, 'output')} draggable>
        Output Node
      </div>

    </aside>
  );
}

nodes和edges是节点和线的对象数组

node:

id: string  唯一标识,用于连线

position: { x: number, y: number }  定位信息

type: string  定义节点的类型

data: {} 传入节点内的数据

自定义节点的话 修改type就可以

edge:

id: string  唯一标识,必填

source: string  连线的起始节点的 id

target: string  连线的结束节点的 id

type: string  线的类型

线的类型有曲线、直线、折线等

拓展

使用中节点类型可能不满足目前要求 可以自定义类型 节点的形状、颜色、选中状态都可以自己调整

GIF22.gif index.tsx

const nodeTypes = {
  floating: FloatingNode,
};
 <ReactFlow
    nodes={nodes}
    edges={edges}
    onNodesChange={onNodesChange}
    onEdgesChange={onEdgesChange}
    onNodeClick={onNodeClick}
    onConnect={onConnect}
    onInit={setReactFlowInstance}
    onDrop={onDrop}
    onDragOver={onDragOver}
    nodeTypes={nodeTypes}
    defaultZoom={1.0} minZoom={1} maxZoom={1.5}
  >
    <Controls />
  </ReactFlow>

Sidebar.tsx

 <div className="dndnode input" onDragStart={(event) => onDragStart(event, 'input')} draggable>
        Input Node
      </div>
      <div className="dndnode" onDragStart={(event) => onDragStart(event, 'default')} draggable>
        Default Node
      </div>
      <div className="dndnode output" onDragStart={(event) => onDragStart(event, 'output')} draggable>
        Output Node
      </div>
      <div className="floating output" onDragStart={(event) => onDragStart(event, 'floating')} draggable>
        Floating Node
</div>

FloatingNode.tsx

export default memo((node: Node) => {
  const { data: { label } } = node
  return (
    <>
      <div>
        {label}
      </div>
      <Handle type="target" position={Position.Top} id="a" />
      <Handle type="source" position={Position.Bottom} id="b" />

      <Handle type="source" position={Position.Right} id="c" />
      <Handle type="target" position={Position.Left} id="d" />
    </>
  );
})