掘金 人工智能 18小时前
浅谈工作流画布引擎 React Flow
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文深入介绍了React Flow,一个基于React的现代化工作流编排库,被广泛应用于百宝箱、扣子等平台,用于构建智能体。文章涵盖了React Flow的简介、使用步骤、节点和边的自定义方法,以及丰富的事件处理机制,包括节点和边的各种交互事件。此外,还提供了性能优化和状态管理的建议,帮助开发者高效构建交互式工作流应用。

💡 React Flow是一个基于React的库,用于构建基于节点的应用程序,它提供了易于使用、高度可定制且渲染快速的特性,内置了多种开箱即用的功能和插件,例如拖动节点、缩放和平移、自定义节点类型和边类型等。

⚙️ 使用React Flow,开发者可以通过简单的步骤创建工作流,包括安装依赖、初始化节点和边。文章详细介绍了如何自定义节点和边,通过注册节点类型和边类型,以及使用useNodesState和useEdgesState等Hooks来管理节点和边的状态,从而实现更灵活的工作流构建。

🖱️ React Flow提供了丰富的事件处理机制,包括节点和边的点击、拖拽、鼠标悬停、删除等事件。开发者可以利用这些事件,实现交互控制、数据管理和连接验证等功能,从而构建出更具交互性和灵活性的工作流应用,例如通过onEdgeClick实现边的信息展示。

React Flow 作为基于 React 的现代化工作流编排工具,通过其声明式渲染协议和可扩展的节点系统,已成为百宝箱、扣子等主流工作流平台的核心画布引擎, 服务于智能体的搭建。

一、React Flow 简介

React Flow 是一个用于构建基于节点的应用程序的库。官方文档 reactflow.dev/learn可以实现自定义节点类型和边,它还视口控件等等组件。

    易于使用:已经具备了许多开箱即用的功能。拖动节点、缩放和平移、选择多个节点和边缘以及添加/删除边都是内置功能。可定制:支持自定义节点类型和边类型。由于自定义节点只是 React 组件,因此你可以实现任何需要的功能,不会被锁定在内置的节点类型中。 自定义边可让你在节点边添加标签、控件和定制逻辑。快速渲染:只渲染发生变化的节点,并确保只显示视口中的节点。内置插件:提供了一些开箱即用的插件,如 <Background /> 可实现一些基本的自定义背景图案;<MiniMap /> 可在屏幕一角显示小版本的图形;<Controls /> 可添加缩放、居中和锁定视口的控件;<Panel /> 可让你轻松将内容放置在视口的顶部;<NodeToolbar /> 可让你呈现连接到节点的工具栏;<NodeResizer /> 可让你轻松为节点添加调整大小的功能。

二、使用步骤

(一)安装

yarn add @xyflow/reactnpm install @xyflow/react

(二)创建第一个 Flow

reactflow 软件包导出 <ReactFlow /> 组件作为默认导出。加上一些节点和边,就可以开始工作了。

import React from 'react';import { ReactFlow } from '@xyflow/react'; import '@xyflow/react/dist/style.css';// 初始化节点// 节点唯一 ID const initialNodes = [  { id: '1', position: { x: 0, y: 0 }, data: { label: '1' } },  { id: '2', position: { x: 0, y: 100 }, data: { label: '2' } },];// 初始化边 // 边存在唯一 IDconst initialEdges = [{ id: 'e1-2', source: '1', target: '2' }]; export default function App() {  return (    <div style={{ width: '100vw', height: '100vh' }}>      <ReactFlow nodes={initialNodes} edges={initialEdges} />    </div>  );}

(三)节点 & 边介绍

1.1 自定义节点

function TextUpdaterNode(props) {  const onChange = useCallback((evt) => {    console.log(evt.target.value);  }, []);   return (    <div className="text-updater-node">      <div>        <label htmlFor="text">Text:</label>        <input id="text" name="text" onChange={onChange} className="nodrag" />      </div>    </div>  );}

1.2 注册节点

const nodeTypes = {  textUpdater: TextUpdaterNode}; function Flow() {  ...  return (    <ReactFlow      nodes={nodes}      edges={edges}      nodeTypes={nodeTypes}      ...    />  );}

1.3 消费自定义节点

const nodes = [  {    id: 'node-1',    type: 'textUpdater',    position: { x: 0, y: 0 },    data: { value: 123 },  },];

批量设置节点setNodes

export function useNodesState<NodeType extends Node>(  initialNodes: NodeType[]): [  nodes: NodeType[],  setNodes: Dispatch<SetStateAction<NodeType[]>>,  onNodesChange: OnNodesChange<NodeType>] {  const [nodes, setNodes] = useState(initialNodes);  const onNodesChange: OnNodesChange<NodeType> = useCallback(    (changes) => setNodes((nds) => applyNodeChanges(changes, nds)),    []  );  return [nodes, setNodes, onNodesChange];}

1.4 自定义边

import React from 'react';import ReactFlow, { BaseEdge, EdgeLabelRenderer } from 'reactflow';// 1. 定义自定义边组件const CustomEdge = ({  id,  sourceX,  sourceY,  targetX,  targetY,  sourcePosition,  targetPosition,  style = {},  markerEnd,}) => {  const [edgePath, labelX, labelY] = getCustomEdge({    sourceX,    sourceY,    sourcePosition,    targetX,    targetY,    targetPosition,  });  return (    <>      <BaseEdge path={edgePath} markerEnd={markerEnd} style={style} />      <EdgeLabelRenderer>        <div          style={{            position: 'absolute',            transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,            background: '#ffcc00',            padding: 5,            borderRadius: 5,            fontSize: 12,            fontWeight: 700,          }}          className="nodrag nopan"        >          自定义边        </div>      </EdgeLabelRenderer>    </>  );};

1.5 边注册

// 2. 定义边类型const edgeTypes = {  custom: CustomEdge,};// 3. 在ReactFlow中使用function Flow() {  return (    <div style={{ width: '100%', height: '500px' }}>      <ReactFlow         edgeTypes={edgeTypes}        nodes={[...]}        edges={[...]}      />    </div>  );}

批量设置边 setEdges

export function useEdgesState<EdgeType extends Edge = Edge>(  initialEdges: EdgeType[]): [  //  edges: EdgeType[],  setEdges: Dispatch<SetStateAction<EdgeType[]>>,  onEdgesChange: OnEdgesChange<EdgeType>] {  const [edges, setEdges] = useState(initialEdges);  const onEdgesChange: OnEdgesChange<EdgeType> = useCallback(    (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),    []  );  return [edges, setEdges, onEdgesChange];}

(四)边事件详解

React Flow 提供了多种边(Edge)相关的事件回调,用于处理用户与边的交互行为。以下是核心边事件及其用法:

1.1 onEdgeClick

1.2 onEdgeDoubleClick

1.3 onEdgeMouseEnter / onEdgeMouseLeave

1.4 onEdgesDelete

1.5 onEdgesChange


2.1 onConnect

2.2 onReconnect

3.1 isValidConnection

3.2 onEdgeUpdateStart / onEdgeUpdateEnd


完整示例代码

import { ReactFlow, useEdgesState, addEdge } from 'reactflow';function FlowComponent() {  const [edges, setEdges, onEdgesChange] = useEdgesState([]);  const onConnect = (connection) =>     setEdges(eds => addEdge({ ...connection, animated: true }, eds));  const onEdgeClick = (event, edge) => alert(`点击边: ${edge.id}`);  return (    <ReactFlow      edges={edges}      onEdgesChange={onEdgesChange}      onConnect={onConnect}      onEdgeClick={onEdgeClick}      isValidConnection={(conn) => conn.source !== conn.target}    />  );}

总结

更多细节可参考 React Flow 官方文档

(五) 节点事件详解

核心节点事件概览

ReactFlow 提供了一系列节点相关的事件处理程序,允许开发者对用户的交互行为做出响应。以下是主要的节点事件类型及其触发时机:

    onNodeClick - 当用户点击节点时触发onNodeDoubleClick - 当用户双击节点时触发onNodeDragStart - 当用户开始拖动节点时触发onNodeDrag - 当用户拖动节点过程中持续触发onNodeDragStop - 当用户停止拖动节点时触发onNodeMouseEnter - 当鼠标进入节点区域时触发onNodeMouseMove - 当鼠标在节点区域内移动时触发onNodeMouseLeave - 当鼠标离开节点区域时触发onNodeContextMenu - 当用户在节点上右键点击时触发onNodesDelete - 当节点被删除时触发

1.1 点击相关事件

onNodeClick 是最常用的节点事件之一,它允许开发者在用户点击节点时执行自定义逻辑。这个事件接收两个参数:事件对象和被点击的节点对象。

const onNodeClick = useCallback((event, node) => {  console.log('Node clicked:', node.id, node.data);  // 可以在这里更新节点状态或执行其他操作}, []);

onNodeDoubleClick 类似于单击事件,但只在快速连续点击两次时触发。这在需要区分单击和双击操作的场景中非常有用。

1.2 拖拽相关事件

ReactFlow 提供了完整的拖拽生命周期事件,使开发者能够精确控制节点的拖拽行为:

const onNodeDragStart = useCallback((event, node) => {  console.log('Drag started for node:', node.id);}, []);const onNodeDrag = useCallback((event, node) => {  // 实时更新节点位置或其他相关状态}, []);const onNodeDragStop = useCallback((event, node) => {  console.log('Drag ended for node:', node.id, 'at position:', node.position);}, []);

1.3 鼠标悬停事件

鼠标悬停相关事件对于创建响应式UI非常有用:

const [hoveredNode, setHoveredNode] = useState(null);const onNodeMouseEnter = useCallback((event, node) => {  setHoveredNode(node.id);}, []);const onNodeMouseLeave = useCallback(() => {  setHoveredNode(null);}, []);

需要注意的是,在某些情况下,自定义节点可能会出现鼠标事件"闪烁"的问题,即鼠标在节点边缘时快速交替触发enter和leave事件。这通常是由于节点边缘检测区域的问题导致的。

1.4 上下文菜单事件

onNodeContextMenu 允许开发者在节点上实现自定义右键菜单功能。默认情况下,浏览器会显示原生上下文菜单,因此通常需要调用 event.preventDefault() 来阻止默认行为。浏览器菜单事件参考见菜单事件

const onNodeContextMenu = useCallback((event, node) => {  event.preventDefault();  console.log('Context menu for node:', node.id);  // 显示自定义上下文菜单}, []);

(六) 一些使用说明

1.1 性能优化

由于 ReactFlow 的事件处理程序会在每次交互时触发,使用 useCallback 来包装处理函数可以避免不必要的重新渲染。

const onNodeClick = useCallback((event, node) => {  // 处理逻辑}, [dependencies]);

Reactflow 中一些方法例如

会有重渲染的问题, 如果需要获取当前工作流画布的缩放比例, 可以考虑一些替代性方法封装成 hooks 使用。如下给出替代方案

// 只订阅 zoom 变化const zoom = useStore(store => store.transform[2]);

1.2 结合 useNodesState 管理节点状态

对于需要根据事件更新节点状态的场景,可以使用 useNodesState 钩子来简化状态管理。

const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);const onNodeClick = useCallback((event, node) => {  setNodes(nds => nds.map(n => {    if (n.id === node.id) {      return {        ...n,        data: { ...n.data, clicked: true }      };    }    return n;  }));}, [setNodes]);

React Flow 作为基于 React 的现代化工作流编排工具,通过其声明式渲染协议和可扩展的节点系统,已成为百宝箱、扣子等主流工作流平台的核心画布引擎, 服务于智能体的搭建。 后续会从渲染协议继续介绍到一般性消费协议,聊一聊如何把画布渲染的数据转化成服务能够消费的数据,供大模型或者其他插件等等技能消费,以最终实现智能体的编排输出。

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

React Flow 工作流编排 React 智能体
相关文章