基于 Zustand 的流程图编辑器全局状态管理与自动保存机制

📖 前言

在现代前端开发中,复杂应用的状态管理一直是一个挑战。特别是在流程图编辑器这类应用中,用户的操作需要实时保存,同时还要保证良好的性能和用户体验。

本文将详细介绍如何基于 Zustand 实现一个全局状态管理系统,并在此基础上构建自动保存机制,让流程图编辑器的数据变更能够自动持久化到后端。

🎯 技术栈

  • 状态管理: Zustand
  • 流程图: ReactFlow
  • 类型检查: TypeScript
  • 构建工具: Vite
  • UI组件: HeroUI
  • HTTP请求: Axios

🏗️ 整体架构

用户操作 → 状态变更 → 自动标记 → 批量保存 → 后端接口
    ↓         ↓         ↓         ↓         ↓
  ReactFlow → Zustand → pendingChanges → triggerSave → API

📦 核心概念

1. 全局状态管理

使用 Zustand 创建一个全局 Store,统一管理流程图的所有数据。

2. 自动保存机制

通过 pendingChanges 标记和 triggerSave 回调,实现数据变更的自动保存。

3. 批量处理

将多个操作合并为一次保存请求,提高性能。

🚀 实现步骤

第一步:创建基础 Store 结构

首先,我们需要定义 Store 的类型和基础结构:

// src/store/flow-data-store.ts

import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';

// 定义节点数据类型
interface FlowNodeData {
  id: string;
  type?: string;
  position: { x: number; y: number };
  data: any;
  [key: string]: any;
}

// 定义连线数据类型
interface FlowEdgeData {
  id: string;
  source: string;
  target: string;
  animated?: boolean;
}

// 定义 Store 接口
interface IFlowDataStore {
  // 数据状态
  nodes: FlowNodeData[];
  edges: FlowEdgeData[];
  metadata: {
    version: string;
    lastModified: string;
    totalNodes: number;
    totalEdges: number;
    description?: string;
  };
  
  // 自动保存相关
  pendingChanges: boolean;
  saveCallback?: () => Promise<void>;
  
  // 操作方法
  addNode: (node: FlowNodeData) => void;
  removeNode: (nodeId: string) => void;
  updateNode: (nodeId: string, updates: Partial<FlowNodeData>) => void;
  addEdge: (edge: FlowEdgeData) => void;
  removeEdge: (edgeId: string) => void;
  updateEdge: (edgeId: string, updates: Partial<FlowEdgeData>) => void;
  
  // 自动保存方法
  setPendingChanges: (pending: boolean) => void;
  triggerSave: () => Promise<void>;
  setSaveCallback: (callback: () => Promise<void>) => void;
}

第二步:实现 Store 核心逻辑

// src/store/flow-data-store.ts (续)

export const useFlowDataStore = create<IFlowDataStore>()(
  persist(
    (set, get) => ({
      // 初始状态
      nodes: [],
      edges: [],
      metadata: {
        version: "1.0.0",
        lastModified: new Date().toISOString(),
        totalNodes: 0,
        totalEdges: 0,
        description: "",
      },
      pendingChanges: false,
      saveCallback: undefined,

      // 添加节点方法
      addNode: (node) => {
        set((state) => ({
          nodes: [...state.nodes, node],
          metadata: {
            ...state.metadata,
            totalNodes: state.nodes.length + 1,
            lastModified: new Date().toISOString(),
          },
        }));
        console.log('➕ 节点已添加:', node.id);
        
        // 🔑 关键:标记有待保存的变更并触发自动保存
        get().setPendingChanges(true);
      },

      // 删除节点方法
      removeNode: (nodeId) => {
        set((state) => {
          const updatedNodes = state.nodes.filter(node => node.id !== nodeId);
          return {
            nodes: updatedNodes,
            metadata: {
              ...state.metadata,
              totalNodes: updatedNodes.length,
              lastModified: new Date().toISOString(),
            },
          };
        });
        console.log('🗑️ 节点已删除:', nodeId);
        
        // 🔑 关键:标记有待保存的变更并触发自动保存
        get().setPendingChanges(true);
      },

      // 添加连线方法
      addEdge: (edge) => {
        set((state) => ({
          edges: [...state.edges, edge],
          metadata: {
            ...state.metadata,
            totalEdges: state.edges.length + 1,
            lastModified: new Date().toISOString(),
          },
        }));
        console.log('➕ 连线已添加:', edge.id);
        
        // 🔑 关键:标记有待保存的变更并触发自动保存
        get().setPendingChanges(true);
      },

      // 设置待保存状态 - 核心方法
      setPendingChanges: (pending) => {
        set({ pendingChanges: pending });
        console.log('📝 待保存状态已更新:', pending);
        
        // 🔑 关键:当有变更且设置了保存回调时,自动触发保存
        if (pending && get().saveCallback) {
          console.log('🚀 检测到变更,自动触发保存...');
          // 异步触发保存,避免阻塞状态更新
          setTimeout(() => {
            get().triggerSave();
          }, 0);
        }
      },

      // 设置保存回调函数
      setSaveCallback: (callback) => {
        set({ saveCallback: callback });
        console.log('🔗 保存回调函数已设置');
      },

      // 触发保存操作 - 核心方法
      triggerSave: async () => {
        const state = get();
        if (state.pendingChanges && state.saveCallback) {
          try {
            console.log('🚀 开始执行批量保存...');
            await state.saveCallback();
            set({ pendingChanges: false });
            console.log('✅ 批量保存完成');
          } catch (error) {
            console.error('❌ 批量保存失败:', error);
          }
        } else {
          console.log('⏭️ 跳过保存:无待保存变更或无保存回调');
        }
      },
    }),
    {
      // 持久化配置
      name: "flow-data-storage",
      storage: createJSONStorage(() => localStorage),
      partialize: (state) => ({
        nodes: state.nodes,
        edges: state.edges,
        metadata: state.metadata,
      }),
    }
  )
);

第三步:实现保存回调函数

// src/pages/mdpt/hooks/useFlowActions.ts

import { useCallback } from 'react';
import useFlowDataStore from '@/store/flow-data-store';
import { saveScriptDraft } from '@/api/employee';

export const useFlowActions = () => {
  const { updateFlowData, getStatistics } = useFlowDataStore();

  // 🔑 核心:自动保存到后端的回调函数
  const autoSaveToBackend = useCallback(async () => {
    try {
      // 获取最新的节点和连线数据
      const latestNodes = useFlowDataStore.getState().nodes;
      const latestEdges = useFlowDataStore.getState().edges;
      
      // 获取统计信息
      const statistics = getStatistics();

      // 构建保存数据
      const flowData = {
        nodes: latestNodes,
        edges: latestEdges,
        timestamp: new Date().toISOString(),
        summary: statistics
      };

      // 格式化数据
      const querycontent = {
        global_data: {},
        data: { ...flowData }
      };
      
      // 调用保存接口
      const content = JSON.stringify(querycontent);
      const success = await saveScriptDraft(content);

      if (success) {
        console.log('✅ 自动保存到后端成功');
        // 保存成功后重置待保存状态
        useFlowDataStore.getState().setPendingChanges(false);
      } else {
        console.error('❌ 自动保存到后端失败');
      }
    } catch (error) {
      console.error('❌ 自动保存到后端时出现错误:', error);
    }
  }, [getStatistics]);

  return {
    autoSaveToBackend,
    // 其他操作方法...
  };
};

第四步:在组件中设置自动保存

// src/pages/mdpt/MdptPage.tsx

import React from 'react';
import useFlowDataStore from '@/store/flow-data-store';
import { useFlowActions } from './hooks/useFlowActions';

function MdptPage() {
  const { setSaveCallback } = useFlowDataStore();
  const { autoSaveToBackend } = useFlowActions();

  // 🔑 关键:设置保存回调函数
  React.useEffect(() => {
    setSaveCallback(autoSaveToBackend);
    console.log('🔗 保存回调函数已设置');
  }, [setSaveCallback, autoSaveToBackend]);

  return (
    <div>
      {/* 流程图编辑器组件 */}
    </div>
  );
}

第五步:实现用户操作处理

// src/pages/mdpt/hooks/useFlowActions.ts (续)

export const useFlowActions = () => {
  const { addNode: addNodeToStore, removeNode: removeNodeFromStore } = useFlowDataStore();

  // 添加节点操作
  const addNode = useCallback(async (nodeType?: string) => {
    const newNode = {
      id: generateNodeId(),
      type: 'custom',
      position: { x: Math.random() * 500 + 50, y: Math.random() * 300 + 50 },
      data: {
        label: getNodeLabel(nodeType),
        description: getNodeDescription(nodeType),
        emoji: getNodeEmoji(nodeType),
        nodeType: nodeType,
      },
    };

    // 🔑 关键:直接调用 Store 方法,自动触发保存
    addNodeToStore(newNode);
    console.log('✅ 节点已添加,自动保存已触发');
  }, []);

  // 删除节点操作
  const deleteNode = useCallback(async (nodeId: string) => {
    // 🔑 关键:直接调用 Store 方法,自动触发保存
    removeNodeFromStore(nodeId);
    console.log('✅ 节点已删除,自动保存已触发');
  }, []);

  return {
    addNode,
    deleteNode,
    autoSaveToBackend,
  };
};

第六步:连线处理

// src/pages/mdpt/MdptPage.tsx (续)

function MdptPage() {
  const { addEdge: addEdgeToStore } = useFlowDataStore();

  // 连线回调处理
  const onConnect = useCallback(async (params: Connection) => {
    const newEdge = {
      id: generateEdgeId(params.source || '', params.target || ''),
      source: params.source!,
      target: params.target!,
      animated: false
    };
    
    // 🔑 关键:直接调用 Store 方法,自动触发保存
    addEdgeToStore(newEdge);
    console.log('✅ 连线已添加,自动保存已触发');
  }, []);

  return (
    <ReactFlow
      onConnect={onConnect}
      // 其他属性...
    />
  );
}

🔍 核心机制解析

1. 自动保存触发流程

// 完整的自动保存流程
用户操作 → 调用 Store 方法 → setPendingChanges(true) → 自动触发 triggerSave() → 调用保存回调 → 发送接口请求

2. 批量处理机制

// 多个操作合并为一次保存
addNode() → pendingChanges: true
addEdge() → pendingChanges: true (已经是true,不重复设置)
updateNode() → pendingChanges: true (已经是true,不重复设置)
// 最后统一保存一次,避免频繁的网络请求

3. 异步触发机制

// 使用 setTimeout 避免阻塞状态更新
setPendingChanges(true) → setTimeout(() => triggerSave(), 0)

🎯 关键技术点

1. 状态标记机制

  • 使用 pendingChanges 标记是否有待保存的变更
  • 避免重复保存和无意义的网络请求

2. 回调函数机制

  • 通过 saveCallback 实现统一的保存入口
  • 支持不同的保存策略(本地存储、后端接口等)

3. 异步处理

  • 使用 setTimeout 避免阻塞状态更新
  • 确保用户操作的响应性

4. 错误处理

  • 完整的错误捕获和处理机制
  • 保存失败时的状态恢复

📊 性能优化

1. 批量处理

// 多个操作合并为一次保存请求
用户快速操作 → 多次 pendingChanges: true → 一次保存请求

2. 智能触发

// 只在有实际变更时才保存
if (pending && saveCallback) {
  // 执行保存
} else {
  // 跳过保存
}

3. 避免无效保存

// 从外部加载数据时不触发保存
syncFromAPI(data) → pendingChanges: false (重置状态)

🧪 最小可行示例

如果你想快速体验这个机制,可以创建一个简单的示例:

// 最小可行示例
import { create } from 'zustand';

interface SimpleStore {
  count: number;
  pendingChanges: boolean;
  saveCallback?: () => Promise<void>;
  
  increment: () => void;
  setPendingChanges: (pending: boolean) => void;
  triggerSave: () => Promise<void>;
  setSaveCallback: (callback: () => Promise<void>) => void;
}

const useSimpleStore = create<SimpleStore>((set, get) => ({
  count: 0,
  pendingChanges: false,
  saveCallback: undefined,

  increment: () => {
    set((state) => ({ count: state.count + 1 }));
    get().setPendingChanges(true);
  },

  setPendingChanges: (pending) => {
    set({ pendingChanges: pending });
    if (pending && get().saveCallback) {
      setTimeout(() => get().triggerSave(), 0);
    }
  },

  triggerSave: async () => {
    const state = get();
    if (state.pendingChanges && state.saveCallback) {
      await state.saveCallback();
      set({ pendingChanges: false });
    }
  },

  setSaveCallback: (callback) => set({ saveCallback: callback }),
}));

// 使用示例
const saveToConsole = async () => {
  console.log('保存到控制台:', useSimpleStore.getState().count);
};

useSimpleStore.getState().setSaveCallback(saveToConsole);

// 每次调用 increment 都会自动保存
useSimpleStore.getState().increment(); // 自动保存
useSimpleStore.getState().increment(); // 自动保存

🎉 总结

这个自动保存机制的核心优势:

  1. 零配置:无需手动调用保存函数
  2. 高性能:批量处理多个操作
  3. 智能触发:只在有变更时才保存
  4. 统一管理:所有保存逻辑集中管理
  5. 易于扩展:支持不同的保存策略

通过这个机制,我们实现了:

  • ✅ 用户操作自动保存
  • ✅ 批量处理提高性能
  • ✅ 统一的错误处理
  • ✅ 良好的用户体验

这个方案可以应用到任何需要自动保存的场景,比如文档编辑器、表单编辑器、代码编辑器等。

📚 扩展阅读


希望这篇技术博客能帮助你理解全局状态管理和自动保存机制的实现原理!如果有任何问题,欢迎讨论交流。

posted @ 2025-08-02 17:01  刘鑫!!  阅读(81)  评论(0)    收藏  举报