
如何做 Tauri 桌面技能管理器的状态管理来自 skills-manage 的 Zustand 业务域拆分实践【免费下载链接】skills-manageDesktop app to manage AI coding agent skills across Claude Code, Cursor, Gemini CLI, Codex, and 20 platforms from one place.项目地址: https://gitcode.com/gh_mirrors/sk/skills-manageskills-manage 是一款 AI 编程技能管理桌面应用可以在一个窗口里统一管理 Claude Code、Cursor、Gemini CLI、Codex 等 20 平台上的 Agent 技能。它的前端基于 React Tauri状态管理选择了 Zustand。本文将带你拆解它的两个核心设计按业务域拆分的 Store 架构以及统一的 Tauri invoke 调用封装并给出可以直接照搬的工程规范。一眼看懂每个业务域一个独立 Store打开 src/stores/ 目录你会看到 10 个 store 文件每一个都对应应用的一个业务模块Store 文件负责的业务域skillStore.ts各平台Agent技能列表与卸载platformStore.ts平台检测、全量扫描、技能计数centralSkillsStore.ts中央技能库唯一真实来源目录collectionStore.ts技能集合、批量安装、导入导出marketplaceStore.ts技能市场、GitHub 源同步与仓库导入discoverStore.ts项目级技能磁盘扫描settingsStore.ts扫描目录、GitHub PAT、自定义 AgentskillDetailStore.ts技能详情页数据obsidianStore.tsObsidian 库集成themeStore.ts主题配色纯前端状态这个拆法有一条明确的项目约定见 CLAUDE.md每个业务域一个独立的 Zustand storestore 内部直接调用invoke()与后端通信不要在组件里直接invoke()。这样做的好处很直接职责单一市场页的加载态不会污染平台页的状态各页面组件只订阅自己关心的 store可测试每个 store 都有独立的单元测试src/test/ 下 15 个*.store.test.ts好定位出问题时先想这是哪个业务域的事答案就是文件名。单个 Store 的状态结构数据 loading error 三件套看 skillStore.ts 就能发现项目的标准模板——每个 store 都由三部分组成interface SkillState { skillsByAgent: Recordstring, ScannedSkill[]; // ① 业务数据 loadingByAgent: Recordstring, boolean; // ② 加载态按 key 细分 error: string | null; // ③ 错误信息 getSkillsByAgent: (agentId: string) Promisevoid; // ④ Actions uninstallSkillFromAgent: (skillId: string, agentId: string) Promisevoid; }两个值得注意的细节加载态按 key 拆分loadingByAgent是Recordstring, boolean因为切换平台和卸载某个技能是不同维度的等待全局一个isLoading会把 UI 锁死。异步 Action 的统一骨架以 getSkillsByAgent 为例流程永远是「置 loading → 调invoke()→ 成功写数据 / 失败写 error → 清 loading」。新写 store 时照抄这个骨架即可。Tauri invoke 封装规范一切走 src/lib/tauri.ts后端Rust通过#[tauri::command]暴露 40 个 IPC 命令前端不能到处散落import { invoke } from tauri-apps/api/core而是统一收口到 src/lib/tauri.ts 这个只有 19 行的薄封装层export function isTauriRuntime(): boolean { /* 检测是否运行在 Tauri 环境中 */ } export const invoke tauriInvoke; // 转发 Tauri 的 invoke export const listen tauriListen; // 转发 Tauri 的 listen这个封装解决了一个很实际的问题同一份前端代码要能在浏览器里跑开发调试、自动化测试也要能在 Tauri 桌面环境跑。规范如下store 内先判断isTauriRuntime()是桌面环境就走真正的invoke()浏览器环境走fixture 兜底数据如 platformStore.ts 内置了假的平台列表skillStore.ts 内置了假技能这样pnpm dev单独起前端、或 Vitest 跑测试时整个应用依然可以点、可以测纯桌面功能直接优雅报错比如 marketplaceStore.ts 的仓库导入在非 Tauri 环境会写入Desktop-only feature错误而不是崩溃。 这是 Tauri 项目的通用最佳实践把环境差异隔离在一个文件里业务 store 只关心要调用哪个命令不关心调用发生在真实窗口还是浏览器。事件流把 listen() 也写进 Storeinvoke 是一问一答但有些操作是长时间任务比如从 GitHub 仓库导入几十个技能。skills-manage 的解法是后端发进度事件store 用listen()订阅并写回状态导入启动时marketplaceStore.ts 会listen(github-import:progress)把每个进度事件写入嵌套的githubImport.importProgress字段UI 进度条随之平滑更新AI 摘要流式生成则复用了 src/lib/explanationStream.ts 封装好的监听器按skillId过滤chunk/complete/error三类事件逐个拼接到 store 里所有 unlisten 句柄都被妥善收集如 githubImportAiUnlisteners在重置时统一清理避免监听器泄漏。对应到产品上就是导入向导里发现 21 个技能 → 逐个预览 → 确认导入的流畅体验初始化与页面联动首屏请求放哪里最后一个容易踩坑的问题是打开应用就加载数据这类请求该由谁发skills-manage 的答案是放在顶层布局组件 AppShell.tsx 的useEffect里调用 platformStore.initialize() 完成拉平台列表 全量扫描两个并行invoke之后 Sidebar、TopBar、全局搜索等组件只需订阅数据不负责发起请求。全局重新扫描也会联动刷新中央库与项目扫描两个 store保证各处计数一致。另外值得一提的是例外情况themeStore.ts 完全不碰后端主题与配色只写localStorage并在首帧渲染前init()防止闪烁。纯 UI 状态与需要 IPC 的业务状态天然分属不同 store——这正是按业务域拆分能自然成立的根本原因。总结可以直接照搬的 4 条规范一个业务域 一个 store文件名即职责禁止组件内直接invoke()状态三件套业务数据 按 key 细分的 loading error: string | nullinvoke 统一收口薄封装invoke/listen/isTauriRuntime()浏览器环境用 fixture 数据兜底桌面专属功能优雅降级长任务走事件流后端 emit 进度事件store 内listen()写回状态并统一管理 unlisten 清理。按这套规范搭建后新功能的开发路径非常固定建 store → 定 State 接口 → 写 invoke 动作 → 组件订阅状态逻辑与 UI 彻底解耦测试也只需针对 store 本身。【免费下载链接】skills-manageDesktop app to manage AI coding agent skills across Claude Code, Cursor, Gemini CLI, Codex, and 20 platforms from one place.项目地址: https://gitcode.com/gh_mirrors/sk/skills-manage创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考