技能标识:zustand-patterns
Zustand 状态管理实战模式。涵盖 Store 设计规范、Slice 工厂复用、persist 持久化、可恢复任务持久化、Electron IPC 联动、Store 测试和常见陷阱。适用于 React + Zustand 项目。
来自 14 个模块共用 Zustand 的生产级应用的实战经验。
typescript
// ✅ 每个功能模块独立 Store
src/modules/video-compressor/store/index.ts → useVideoCompressorStore
src/modules/video-upscaler/store/index.ts → useVideoUpscalerStore
// ❌ 不要把所有状态塞进一个全局 Store
src/store/globalStore.ts → useGlobalStore // 千万别这样
typescript
// Hook 导出用 use 前缀 + 模块名 + Store
export const useVideoCompressorStore = create
// 文件名:index.ts 或 {moduleName}Store.ts
typescript
// ✅ 先定义接口,再实现
interface VideoCompressorStore {
// — 状态 —
inputFiles: string[];
outputDir: string;
targetSizeMB: number;
logs: LogEntry[];
// — Actions —
setInputFiles: (files: string[]) => void;
addInputFiles: (files: string[]) => void;
removeInputFile: (path: string) => void;
reset: () => void;
}
export const useVideoCompressorStore = create
persist(
(set) => ({
// 实现...
}),
{ name: video-compressor }
)
);
typescript
// set 前缀:简单赋值
setInputFiles: (files) => set({ inputFiles: files }),
setTargetSizeMB: (size) => set({ targetSizeMB: size }),
// add/remove 前缀:集合操作
addInputFiles: (files) => set((state) => ({
inputFiles: [...state.inputFiles, ...files.filter(f => !state.inputFiles.includes(f))]
})),
removeInputFile: (path) => set((state) => ({
inputFiles: state.inputFiles.filter(p => p !== path)
})),
// clear 前缀:清空
clearInputFiles: () => set({ inputFiles: [] }),
clearLogs: () => set({ logs: [] }),
// reset:恢复初始状态
reset: () => set({ inputFiles: [], outputDir: , targetSizeMB: 50, logs: [] }),
多个 Store 有相同的状态片段时,用 Slice 工厂提取:
typescript
// store/slices/createProcessingSlice.ts
export interface ProcessingSliceState
isProcessing: boolean;
progress: TProgress;
setIsProcessing: (isProcessing: boolean) => void;
setProgress: (progress: TProgress) => void;
resetProcessing: () => void;
}
export function createProcessingSlice
set: SetState
defaultProgress: TProgress = 0 as TProgress,
): ProcessingSliceState
return {
isProcessing: false,
progress: defaultProgress,
setIsProcessing: (isProcessing) => set({ isProcessing } as any),
setProgress: (progress) => set({ progress } as any),
resetProcessing: () => set({ isProcessing: false, progress: defaultProgress } as any),
};
}
typescript
interface MyModuleStore extends ProcessingSliceState {
inputFiles: string[];
// ...
}
const useMyModuleStore = create
...createProcessingSlice(set), // 展开混入
inputFiles: [],
// ...
}));
typescript
// progress 不一定是 number,可以是复杂对象
interface SceneAnalyzerProgress {
phase: splitting | analyzing | done;
current: number;
total: number;
}
interface SceneAnalyzerStore extends ProcessingSliceState
// ...
}
const store = create
...createProcessingSlice
// ...
}));
3. 持久化
typescript
import { persist } from zustand/middleware;
const useSettingsStore = create
persist(
(set) => ({ / ... / }),
{
name: settings-storage, // localStorage key
version: 1, // 迁移版本
partialize: (state) => ({ // 只持久化部分字段
outputDir: state.outputDir,
targetSizeMB: state.targetSizeMB,
// ❌ 不持久化 isProcessing、logs 等运行时状态
}),
}
)
);
typescript
// ✅ 持久化:用户配置、偏好设置
partialize: (state) => ({
outputDir: state.outputDir,
quality: state.quality,
encoder: state.encoder,
})
// ❌ 不持久化:运行时状态
// isProcessing, progress, logs, error — 这些重启后应该重置
Electron 中 localStorage 可用(渲染进程),但如果需要主进程访问,用 electron-store:
typescript
import { persist, createJSONStorage } from zustand/middleware;
// 自定义 storage adapter 走 IPC
const electronStorage = createJSONStorage(() => ({
getItem: (name) => ipcRenderer.invoke(store:get, name),
setItem: (name, value) => ipcRenderer.invoke(store:set, name, value),
removeItem: (name) => ipcRenderer.invoke(store:remove, name),
}));
远程异步任务(如 AI 视频生成)提交后,应用重启需要恢复轮询:
typescript
interface RecoverableTaskState {
needsPollingRecovery: boolean;
clearPollingRecoveryFlag: () => void;
}
function createRecoverablePersistConfig
name,
taskField,
isTaskPending,
additionalFields = [],
}: {
name: string;
taskField: keyof T;
isTaskPending: (task: any) => boolean;
additionalFields?: (keyof T)[];
}) {
return {
name,
partialize: (state: T) => {
const result: any = { [taskField]: state[taskField] };
for (const field of additionalFields) {
result[field] = state[field];
}
return result;
},
onRehydrate: (state: T) => {
// 检查是否有需要恢复的 pending 任务
const tasks = (state as any)[taskField] || [];
if (Array.isArray(tasks) && tasks.some(isTaskPending)) {
(state as any).needsPollingRecovery = true;
}
},
};
}
// 使用
const store = create
persist(
(set, get) => ({ / ... / }),
createRecoverablePersistConfig({
name: video-upscaler,
taskField: tasks,
isTaskPending: (task) => task.status === processing && !!task.remoteId,
additionalFields: [config],
})
)
);
// 组件中恢复轮询
useEffect(() => {
if (store.needsPollingRecovery) {
store.clearPollingRecoveryFlag();
store.recoverPolling();
}
}, []);
✅ 适用:远程 API 任务(视频超分、AI 生成)— 服务端继续处理
❌ 不适用:本地进程任务(FFmpeg 压缩)— 进程随应用关闭而终止
typescript
// 渲染进程:监听主进程事件更新 Store
useEffect(() => {
const listeners = [
window.electronAPI.on(module:progress, (progress: number) => {
useMyStore.getState().setProgress(progress);
}),
window.electronAPI.on(module:complete, () => {
useMyStore.getState().setIsProcessing(false);
useMyStore.getState().setProgress(100);
}),
window.electronAPI.on(module:error, (error: string) => {
useMyStore.getState().setIsProcessing(false);
useMyStore.getState().setError(error);
}),
window.electronAPI.on(module:log
以下为平台配置的接入选项,并非逐项实测通过。能否安装取决于客户端支持、技能来源和运行环境:
帮我安装 SkillHub 和 zustand-patterns-1776284778 技能
设置 SkillHub 为我的优先技能安装源,然后帮我安装 zustand-patterns-1776284778 技能
skillhub install zustand-patterns-1776284778
文件大小: 4.93 KB | 发布时间: 2026-4-16 16:08