# 状态管理 > **文档版本**:v1.1 > **最后更新**:2025-01-18 --- ## 目录 1. [状态分层策略](#1-状态分层策略) 2. [Zustand Store 设计](#2-zustand-store-设计) 3. [TanStack Query 配置](#3-tanstack-query-配置) 4. [自定义 Query Hook](#4-自定义-query-hook) --- ## 1. 状态分层策略 ``` ┌─────────────────────────────────────────────────┐ │ 服务端状态 │ │ (TanStack Query 管理) │ │ - 项目列表、分镜数据、资源数据等 │ └─────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────┐ │ 客户端状态 │ │ (Zustand 管理) │ │ - 当前选中项、UI 状态、编辑器状态 │ └─────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────┐ │ 组件本地状态 │ │ (useState 管理) │ │ - 表单输入、临时 UI 状态 │ └─────────────────────────────────────────────────┘ ``` ### 1.1 状态分类原则 | 状态类型 | 管理方式 | 示例 | | ------------------ | -------------- | ---------------------------- | | **服务端状态** | TanStack Query | 项目列表、分镜数据、用户信息 | | **全局客户端状态** | Zustand | 当前选中项、UI 展开/折叠状态 | | **组件本地状态** | useState | 表单输入、临时 UI 状态 | --- ## 2. Zustand Store 设计 ### 2.1 应用全局状态 ```typescript // src/stores/appStore.ts import { create } from "zustand"; import { devtools, persist } from "zustand/middleware"; interface AppState { // 状态 isInitialized: boolean; currentProjectId: string | null; user: User | null; // 操作 setInitialized: (value: boolean) => void; setCurrentProject: (projectId: string | null) => void; setUser: (user: User | null) => void; reset: () => void; } export const useAppStore = create()( devtools( persist( (set) => ({ // 初始状态 isInitialized: false, currentProjectId: null, user: null, // 操作 setInitialized: (value) => set({ isInitialized: value }), setCurrentProject: (projectId) => set({ currentProjectId: projectId }), setUser: (user) => set({ user }), reset: () => set({ isInitialized: false, currentProjectId: null, user: null, }), }), { name: "app-store", partialize: (state) => ({ currentProjectId: state.currentProjectId, }), }, ), { name: "AppStore" }, ), ); ``` ### 2.2 编辑器状态 ```typescript // src/stores/editorStore.ts import { create } from "zustand"; import { devtools } from "zustand/middleware"; interface EditorState { // 选中状态 selectedStoryboardId: string | null; selectedStoryboard BoardItems: string[]; // 播放状态 isPlaying: boolean; currentTime: number; duration: number; // 分镜看板状态 timelineZoom: number; timelineScrollPosition: number; // 操作 selectStoryboard: (id: string | null) => void; selectStoryboard BoardItems: (ids: string[]) => void; toggleStoryboard BoardItem: (id: string) => void; setPlaying: (playing: boolean) => void; setCurrentTime: (time: number) => void; setDuration: (duration: number) => void; setStoryboard BoardZoom: (zoom: number) => void; setStoryboard BoardScrollPosition: (position: number) => void; reset: () => void; } const initialState = { selectedStoryboardId: null, selectedStoryboard BoardItems: [], isPlaying: false, currentTime: 0, duration: 0, timelineZoom: 1, timelineScrollPosition: 0, }; export const useEditorStore = create()( devtools( (set, get) => ({ ...initialState, selectStoryboard: (id) => set({ selectedStoryboardId: id }), selectStoryboard BoardItems: (ids) => set({ selectedStoryboard BoardItems: ids }), toggleStoryboard BoardItem: (id) => { const current = get().selectedStoryboard BoardItems; const isSelected = current.includes(id); set({ selectedStoryboard BoardItems: isSelected ? current.filter((i) => i !== id) : [...current, id], }); }, setPlaying: (playing) => set({ isPlaying: playing }), setCurrentTime: (time) => set({ currentTime: time }), setDuration: (duration) => set({ duration }), setStoryboard BoardZoom: (zoom) => set({ timelineZoom: Math.max(0.1, Math.min(5, zoom)) }), setStoryboard BoardScrollPosition: (position) => set({ timelineScrollPosition: position }), reset: () => set(initialState), }), { name: "EditorStore" }, ), ); ``` ### 2.3 UI 状态 ```typescript // src/stores/uiStore.ts import { create } from "zustand"; import { devtools, persist } from "zustand/middleware"; type LeftPanelTab = "project" | "library"; type RightPanelTab = "imagePrompt" | "angleTransform" | "cameraAdjust"; interface UIState { // 侧边栏状态 leftSidebarCollapsed: boolean; rightSidebarCollapsed: boolean; leftPanelActiveTab: LeftPanelTab; rightPanelActiveTab: RightPanelTab; // 分镜看板状态 timelineExpanded: boolean; // AI 面板 aiPanelVisible: boolean; // 弹窗状态 createProjectModalOpen: boolean; exportModalOpen: boolean; settingsModalOpen: boolean; // 引导状态 showOnboarding: boolean; // 操作 toggleLeftSidebar: () => void; toggleRightSidebar: () => void; setLeftPanelTab: (tab: LeftPanelTab) => void; setRightPanelTab: (tab: RightPanelTab) => void; toggleStoryboard Board: () => void; toggleAIPanel: () => void; setCreateProjectModalOpen: (open: boolean) => void; setExportModalOpen: (open: boolean) => void; setSettingsModalOpen: (open: boolean) => void; setShowOnboarding: (show: boolean) => void; } export const useUIStore = create()( devtools( persist( (set, get) => ({ // 初始状态 leftSidebarCollapsed: false, rightSidebarCollapsed: false, leftPanelActiveTab: "project", rightPanelActiveTab: "imagePrompt", timelineExpanded: true, aiPanelVisible: false, createProjectModalOpen: false, exportModalOpen: false, settingsModalOpen: false, showOnboarding: true, // 操作 toggleLeftSidebar: () => set({ leftSidebarCollapsed: !get().leftSidebarCollapsed }), toggleRightSidebar: () => set({ rightSidebarCollapsed: !get().rightSidebarCollapsed }), setLeftPanelTab: (tab) => set({ leftPanelActiveTab: tab }), setRightPanelTab: (tab) => set({ rightPanelActiveTab: tab }), toggleStoryboard Board: () => set({ timelineExpanded: !get().timelineExpanded }), toggleAIPanel: () => set({ aiPanelVisible: !get().aiPanelVisible }), setCreateProjectModalOpen: (open) => set({ createProjectModalOpen: open }), setExportModalOpen: (open) => set({ exportModalOpen: open }), setSettingsModalOpen: (open) => set({ settingsModalOpen: open }), setShowOnboarding: (show) => set({ showOnboarding: show }), }), { name: "ui-store", partialize: (state) => ({ leftSidebarCollapsed: state.leftSidebarCollapsed, rightSidebarCollapsed: state.rightSidebarCollapsed, leftPanelActiveTab: state.leftPanelActiveTab, timelineExpanded: state.timelineExpanded, showOnboarding: state.showOnboarding, }), }, ), { name: "UIStore" }, ), ); ``` --- ## 3. TanStack Query 配置 ### 3.1 Query Provider ```typescript // src/app/providers/QueryProvider.tsx import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 1000 * 60 * 5, // 5 分钟 gcTime: 1000 * 60 * 30, // 30 分钟 (原 cacheTime) retry: 3, refetchOnWindowFocus: false, }, mutations: { retry: 1, }, }, }); export function QueryProvider({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` ### 3.2 配置说明 | 配置项 | 值 | 说明 | | ---------------------- | ------- | ------------------------ | | `staleTime` | 5 分钟 | 数据被视为过期的时间 | | `gcTime` | 30 分钟 | 缓存数据的保留时间 | | `retry` | 3 次 | 请求失败重试次数 | | `refetchOnWindowFocus` | false | 窗口聚焦时不自动重新请求 | --- ## 4. 自定义 Query Hook ### 4.1 查询键设计 ```typescript // src/hooks/api/useStoryboards.ts export const storyboardKeys = { all: ["storyboards"] as const, lists: () => [...storyboardKeys.all, "list"] as const, list: (projectId: string) => [...storyboardKeys.lists(), projectId] as const, details: () => [...storyboardKeys.all, "detail"] as const, detail: (id: string) => [...storyboardKeys.details(), id] as const, }; ``` ### 4.2 查询 Hook ```typescript // 获取分镜列表 export function useStoryboards(projectId: string) { return useQuery({ queryKey: storyboardKeys.list(projectId), queryFn: () => storyboardApi.getList(projectId), enabled: !!projectId, }); } // 获取单个分镜 export function useStoryboard(id: string) { return useQuery({ queryKey: storyboardKeys.detail(id), queryFn: () => storyboardApi.getById(id), enabled: !!id, }); } ``` ### 4.3 变更 Hook ```typescript // 创建分镜 export function useCreateStoryboard() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: CreateStoryboardDto) => storyboardApi.create(data), onSuccess: (_, variables) => { queryClient.invalidateQueries({ queryKey: storyboardKeys.list(variables.projectId), }); }, }); } // 更新分镜 export function useUpdateStoryboard() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, data }: { id: string; data: UpdateStoryboardDto }) => storyboardApi.update(id, data), onSuccess: (result) => { queryClient.setQueryData(storyboardKeys.detail(result.id), result); queryClient.invalidateQueries({ queryKey: storyboardKeys.lists(), }); }, }); } // 删除分镜 export function useDeleteStoryboard() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (id: string) => storyboardApi.delete(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: storyboardKeys.lists(), }); }, }); } ``` ### 4.4 使用示例 ```typescript function StoryboardPanel({ projectId }: { projectId: string }) { // 查询数据 const { data: storyboards, isLoading, error } = useStoryboards(projectId); // 变更操作 const createMutation = useCreateStoryboard(); const updateMutation = useUpdateStoryboard(); const deleteMutation = useDeleteStoryboard(); const handleCreate = async (data: CreateStoryboardDto) => { try { await createMutation.mutateAsync(data); toast.success('创建成功'); } catch (error) { toast.error('创建失败'); } }; if (isLoading) return ; if (error) return ; return (
{storyboards?.map((storyboard) => ( ))}
); } ``` --- ## 相关文档 - [API 层设计](./06-api-layer.md) - [项目结构](./02-project-structure.md) - [性能优化](./10-performance.md) --- **最后更新**:2025-01-18 | **版本**:v1.1