React Flow使用指南(二)

504 阅读2分钟

上一篇 《React Flow使用指南(一)》介绍了自定义节点等基本操作,接下来就该实现一个真正的流程图了。

20250304215549_rec_.gif

父组件的设计

如何实现外部访问内部数据?

<ReactFlowProvider />组件是一个 上下文提供程序 ,它使得在组件外部访问内部状态成为可能,并且reactflow提供的许多钩子都依赖于此组件才能工作。更多hook

function Flow() {
  return (
    <ReactFlowProvider>
      <ComponentsSideBar />
      <FlowContent />
    </ReactFlowProvider>
  );
}
export function ComponentsSideBar() {
 ...
 // 该hook只有在使用的组件是<ReactFlowProvider /> 的子组件时才会起作用
 const nodes = useNodes() // 获取全部node节点
 const edges = useEdges(); // 获取全部edge边
 const nodes = useReactFlow();
 nodes.setNodes((prev) => prev.map((node) => ({ ...node, position: { x: node.position.x + 10, y: node.position.y } })))
 // 该hook返回一个ReactFlowInstance可用于更新节点和边、操作视口或查询流的当前状态的钩子。
 ...
}

拖拽添加节点

简单的拖拽添加节点,可以通过原生 API draggable 实现。 在 SideBar 组件中触发节点的 onDragStart 事件,然后在 Flow组件中通过 ReactFlow onDrop 来接收。

// components-side-bar.tsx
export function ComponentsSideBar() {
  const onDragStart = (
    event: React.DragEvent<HTMLDivElement>,
    nodeType: string,
    data?: any,
  ) => {
  // 拖拽的节点类型以及节点默认数据,'application/reactflow'确保拖拽时设置的数据类型与读取时一致
    event.dataTransfer.setData('application/reactflow',JSON.stringify({ nodeType, data }));
    event.dataTransfer.effectAllowed = 'move';
  };

  const style = {
    width: 100,
    border: '1px solid #d6d4d4',
    borderRadius: 4,
    padding: '4px',
    marginBottom: '10px',
  };
  return (
    <div style={{ width: 120, border: '1px solid #d6d4d4', padding: '10px' }}>
      <div
        style={style}
        onDragStart={event => onDragStart(event, 'rounded', { num: 1 })}
        draggable
      >
        Rounded Node
      </div>
      <div
        style={style}
        onDragStart={event => onDragStart(event, 'input')}
        draggable
      >
        Input Node
      </div>
      <div
        style={style}
        onDragStart={event => onDragStart(event, 'output')}
        draggable
      >
        Output Node
      </div>
    </aside>
  );
}
// flow.tsx
...
export function FlowContent(props: ReactFlowProps) {
  ...
  const { screenToFlowPosition } = useReactFlow();

// `onDrop` 和 `onDragOver` 是用于处理外部元素拖放到画布的关键事件。
// onDragOver当拖动的元素悬停在画布上时触发,必须调用event.preventDefault()以允许放置。
  const onDragOver = (event: React.DragEvent) => {
    event.preventDefault();
    event.dataTransfer.dropEffect = 'move';
  };
// 当元素被释放到画布时触发,用于创建新节点。
  const onDrop = (event: React.DragEvent) => {
    event.preventDefault();
    const type = event.dataTransfer.getData('application/reactflow');
    if (!type) {
      return;
    }
    try {
    // 解析json串
      const { data, nodeType } = JSON.parse(type);
      // 将屏幕坐标转换为画布坐标
      const position = screenToFlowPosition({
        x: event.clientX,
        y: event.clientY,
      });
      const newNode = {
        id: getId(),
        position,
        type: nodeType,
        data,
      };
      setNodes(nds => nds.concat(newNode));
    } catch (e) {
      console.log(e);
    }
  };

  return (
    <div className={cx('react-flow')}>
      <ReactFlow
        ...
        onDrop={onDrop}
        onDragOver={onDragOver}
        {...props}
      /> 
    </div>
  );
}

连线

在画布上连线的时候,会触发 ReactFlow onConnect 事件,并提供连线信息。

export function FlowContent(props: ReactFlowCustomizeProps) {
  ...
  const onConnect = useCallback(
    (params: Connection) => setEdges(eds => addEdge(params, eds)),
    [],
  );

  return (
    <div className={cx('react-flow')}>
      <ReactFlow onConnect={onConnect} .../>
    </div>
  );
}

如果需要设置连线类型,或者设置其他连线的信息,都可以通过 addEdge 的第一个参数来设置。 如果需要自定义连接中的线的样式,可以使用 connectionLineComponent,具体可以参考官方示例