用React Flow打造可视化低代码工作流:从零构建智能Agent开发平台

89 阅读6分钟

想象一下,用拖拽的方式就能构建复杂的工作流,让非技术人员也能玩转AI应用开发——这就是低代码的魅力!

引言:为什么低代码正在改变游戏规则?

大家好,我是你们的老朋友FogLetter,今天要跟大家分享一个超级有趣的技术——React Flow

最近在学习一个全栈项目,其中有个需求是要让用户能够可视化地构建AI工作流。想象一下:用户拖拽几个节点,连几条线,就能创建一个智能客服系统或者内容生成工具。这听起来很酷,但实现起来会不会很复杂?

答案是:用React Flow,一切都变得简单!

第一章:初识React Flow - 可视化工作流的"乐高积木"

什么是React Flow?

React Flow是一个专门为React设计的可视化工作流编辑库。它提供了:

  • 画布(Canvas) - 你的创作舞台
  • 节点(Node) - 工作流的基本构建块
  • 边(Edge) - 连接节点的关系线
  • 完整的交互支持 - 拖拽、连接、缩放...

最小可行示例:5分钟上手

先来看一个最简单的例子,感受一下React Flow的魅力:

"use client";
import React from "react";
import ReactFlow, { Background } from "reactflow";
import 'reactflow/dist/style.css';

export default function App() {
  const nodes = [
    {
      id: '1',
      position: { x: 100, y: 100 },
      data: { label: '开始节点' }
    },
    {
      id: '2',
      position: { x: 300, y: 100 },
      data: { label: '结束节点' }
    }
  ]
  const edges = [
    {
      id: 'e1-2',
      source: '1',
      target: '2',
    }
  ];
  
  return (
    <div style={{width: '100vw', height: '100vh'}}>
      <ReactFlow nodes={nodes} edges={edges}>
        <Background />
      </ReactFlow>
    </div>
  )
}

看到没?不到30行代码,我们就创建了一个完整的工作流编辑器!两个节点,一条连接线,还有漂亮的网格背景。

第二章:实战进阶 - 构建可交互的工作流编辑器

光有静态节点还不够,我们要让用户能够动态操作工作流。来看看我是如何实现的:

核心功能设计

在我的项目中,我需要实现以下功能:

  • ✅ 动态添加/删除节点
  • ✅ 双击编辑节点内容
  • ✅ 自动保存到数据库
  • ✅ 从数据库加载工作流

代码深度解析

"use client";
import React, { useState, useEffect } from "react";
import ReactFlow, { Background, Controls, Node, Edge } from 'reactflow';
import 'reactflow/dist/style.css';
import { supabase } from '@/lib/supabaseClient';

export default function FlowEditor() {
  const [nodes, setNodes] = useState<Node[]>([]);
  const [edges, setEdges] = useState<Edge[]>([]);
  const [nodeId, setNodeId] = useState(2);

  // 添加节点 - 核心逻辑
  const addNode = () => {
    const newId = String(nodeId);
    const newNode: Node = {
      id: newId,
      position: { x: 100 + nodeId * 50, y: 100 },
      data: { label: `节点${nodeId}` }
    }
    
    setNodes((nodes) => [...nodes, newNode]);
    
    // 自动连接新节点
    if (nodes.length > 0) {
      setEdges((edges) => [
        ...edges,
        {
          id: `e${nodeId-1}-${newId}`,
          source: String(nodeId-1),
          target: newId
        }
      ]);
    }
    
    setNodeId(id => id + 1);
  }

  // 删除节点 - 注意边界情况处理
  const removeNode = () => {
    if (nodes.length <= 1) return;
    
    const lastNode = nodes[nodes.length - 1];
    setNodes((nodes) => nodes.slice(0, -1));
    setEdges((edges) => 
      edges.filter(e => e.target !== lastNode.id)
    );
    setNodeId(id => id - 1);
  }

  // 保存到Supabase - 数据持久化
  const saveFlow = async () => {
    const { error } = await supabase.from('flows').insert({
      name: 'demo',
      nodes,
      edges
    });
    
    if (error) {
      console.error('保存失败:', error);
    } else {
      alert('保存成功');
    }
  }

  // 双击编辑 - 提升用户体验
  const onNodeDoubleClick = (_:React.MouseEvent, node: Node) => {
    const newLabel = prompt('请输入新的节点内容', node.data.label as string);
    if (newLabel !== null && newLabel.trim() !== '') {
      setNodes((nodes) => nodes.map((n) =>
        n.id === node.id ? { ...n, data: { label: newLabel } } : n
      ));
    }
  }

  // 加载数据 - 组件初始化
  useEffect(() => {
    const loadFlow = async () => {
      const { data } = await supabase
        .from('flows')
        .select('*')
        .order('created_at', { ascending: false })
        .limit(1)
        .single();
        
      if(data) {
        setNodes(data.nodes || []);
        setEdges(data.edges || []);
        const maxId = data.nodes?.map((n: Node) => Number(n.id))
          ?.reduce((a: number, b: number) => Math.max(a, b), 1) || 1;
        setNodeId(maxId + 1);
      }
    }
    loadFlow();
  }, []);

  return (
    <div style={{ width: '100%', height: '100vh' }}>
      <div style={{ marginBottom: 10 }}>
        <button onClick={addNode} style={{ marginRight: 10 }}>添加节点</button>
        <button onClick={removeNode} style={{ marginRight: 10 }}>删除节点</button>
        <button onClick={saveFlow}>保存到数据库</button>
      </div>
      
      <ReactFlow
        nodes={nodes}
        onNodeDoubleClick={onNodeDoubleClick}
        edges={edges}
        fitView
      >
        <Background />
        <Controls />
      </ReactFlow>
    </div>
  )
}

技术要点解析

  1. 状态管理:使用useState管理节点、边和节点ID
  2. 副作用处理useEffect在组件挂载时加载数据
  3. 数据持久化:集成Supabase进行实时数据存储
  4. 用户体验:双击编辑、自动连接、边界情况处理

第三章:数据库设计 - 让工作流"记住"一切

Prisma Schema设计

在我的项目中,使用MySQL + Prisma来存储工作流数据:

model Flow {
  id        String   @id @default(cuid())
  name      String
  nodes     Json
  edges     Json
  userId    String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  
  @@map("flows")
}

为什么选择JSON字段?

对于工作流这种半结构化数据,JSON字段是最佳选择:

  • 🔧 灵活性:节点和边的结构可能经常变化
  • 🚀 开发效率:无需频繁修改数据库schema
  • 📦 存储效率:整个工作流作为一个文档存储

第四章:应用场景 - 低代码如何赋能AI开发?

场景一:AI工作流编排

在我的项目中,React Flow用于构建AI Agent开发平台

用户输入 → 意图识别 → LLM处理 → 插件执行 → 结果输出

每个步骤都是一个节点,用户可以:

  • 拖拽调整流程顺序
  • 配置每个节点的参数
  • 测试整个工作流的效果

场景二:智能内容生成

结合我之前做的狗狗照片生成单词识别AI,可以构建这样的工作流:

{
  "nodes": [
    { "type": "input", "label": "用户输入描述" },
    { "type": "coze_api", "label": "生成狗狗图片" },
    { "type": "ocr", "label": "图片文字识别" },
    { "type": "llm", "label": "内容优化" },
    { "type": "output", "label": "最终结果" }
  ]
}

场景三:数据爬虫与处理

结合虚拟列表+爬虫功能,构建数据处理流水线:

爬虫节点 → 数据清洗 → 内容分析 → 结果展示

第五章:高级技巧与最佳实践

自定义节点组件

基础节点不够用?可以创建完全自定义的节点:

const CustomNode = ({ data }) => {
  return (
    <div className="custom-node">
      <div className="node-header">{data.label}</div>
      <div className="node-content">
        <input 
          type="text" 
          placeholder="配置参数" 
          value={data.config} 
          onChange={data.onConfigChange}
        />
      </div>
      <div className="node-handle">
        <Handle type="source" position="bottom" />
      </div>
    </div>
  );
}

const nodeTypes = {
  custom: CustomNode,
};

性能优化技巧

  1. 虚拟化渲染:当节点数量过多时,使用虚拟化技术
  2. 选择性重渲染:使用useMemoReact.memo优化性能
  3. 增量保存:实时保存用户操作,避免数据丢失

错误处理与用户体验

// 添加连接验证
const isValidConnection = (connection) => {
  // 防止循环连接
  if (connection.source === connection.target) return false;
  
  // 防止重复连接
  const existingConnection = edges.find(edge => 
    edge.source === connection.source && 
    edge.target === connection.target
  );
  
  return !existingConnection;
};

第六章:思考与展望

低代码的未来

通过这个项目,我深刻体会到低代码不是要取代程序员,而是放大程序员的价值。我们可以:

  • 🛠️ 构建平台:为特定领域创建专业工具
  • 🔄 提升效率:减少重复性的界面开发工作
  • 🎨 赋能创新:让业务专家也能参与应用构建

技术选型的思考

为什么选择React Flow而不是其他库?

  • 生态成熟:丰富的插件和社区支持
  • TypeScript友好:完整的类型定义
  • 定制性强:从样式到交互都可以深度定制
  • 性能优秀:针对大型工作流做了优化

结语:每个人都是创造者

记得项目上线后,产品经理跑来跟我说:"原来我也能搭建AI工作流了!" 那一刻,我真正感受到了技术的价值——不是让复杂的事情变得更复杂,而是让复杂的事情变得简单

React Flow就像给了我们一套可视化编程的"乐高积木",让我们能够:

"用拖拽的方式,构建智能的未来"

如果你也对低代码和可视化开发感兴趣,不妨从这个小项目开始。相信我,一旦你体验过"拖拽即编程"的魅力,就再也回不去了!