用途与定位
该技能面向使用 React Native/Expo 与 Zustand 5 的客户端状态管理场景,目标是把客户端状态约束为“薄 store + 纯 reducer + 先写盘后发布”的结构。它不提供运行时库,而是一套规则、模板和检查脚本,用于在项目中添加 store、action、selector,或从 Redux、Context、MobX、persist+AsyncStorage 迁移时保持一致性。
主要能力
- store 结构约束:每个领域一个 store,由
create<Domain>Store(deps)工厂生成 vanilla store(createStore来自zustand/vanilla),在根布局的useState初始化器与test/test-app.tsx中各创建一次;禁止create from zustand、模块级 store(除contracts/stores.json中列出的 OS 镜像 store),禁止在 feature/UI 代码中调用工厂。 - 持久化顺序:变更先交给 reducer(被拒绝或无变化返回同一对象,不写不渲染),通过工厂接收的 port 同步写入,检查写入结果,最后才
set。写入失败只发布失败(返回 Result 或…Failure字段),屏幕不显示未落盘的值。服务产生的状态(如 Premium 权益)也必须通过带写入的 store 方法进入,而不是仅发布型 dispatch。 - 纯 reducer 与用例表:
<domain>Reducer(state, action)不导入运行时代码、不修改状态、不读时钟或随机数(时间与 id 由 action 传入),无变化返回同一对象;<name>-reducer-cases.ts以纯数据承载规则(至少一个same: true用例),由测试与check-reducers执行。 - 选择器规则:选择器返回原始值或状态中已有引用;构造值须经
useShallow;禁止无选择器的 store hook 和相等性参数。Zustand 5 中每次返回新引用的选择器会触发 “Maximum update depth exceeded”。 - 跨 store 写入:通过
updateAndPublish(stores, () => tasks.removeList(id))作为一次事务,并在contracts/stores.json的crossStoreWrites中登记;避免两次 store 调用或裸写导致半更新。 - 状态归属:服务端数据留在 TanStack Query 缓存,路由参数归 Expo Router,store 只持有客户端状态(本地行、UI 选择、偏好、会话)。store 内禁止 fetch、React Query、API 客户端、async/await、
.then、zustand persist 中间件、AsyncStorage/MMKV/SQLite 等。 - 可撤销会话:每次会话一个 store 加纯 session reducer;
past留在内存用于即时撤销;写入形式保存 start、命令日志和快照;恢复时重放日志重建撤销历史,重放不一致时保留快照,并以暂停状态返回;同样先持久化再发布。 - 模板与示例:
templates/src/stores/提供 lists/tasks 的 reducer、用例表、选择器、store 及测试;templates/src/stores/session/提供可撤销会话;examples/use-tasks-model.ts、examples/app-root.tsx、examples/app-providers.tsx展示组合方式。 - 检查脚本:
scripts/check-stores.mjs对src/做静态规则检查(支持--src-dir、--stores-dir、--contract选项);scripts/check-reducers.mjs在 Node 中运行每个 reducer 的用例表和通用不变量,以及 session reducer 规则。两者输出RESULT: PASS表示通过,FAIL行会指出文件、规则和修复方式。
使用方式
- 阅读
references/store-pattern.md;决定状态归属或迁移时阅读references/state-homes.md;构建撤销功能前阅读references/undoable-session.md。 - 检查前置条件:
src/core/result.ts、数据层(persistence-and-migrations的仓库或 document service)、组合根(architecture-and-boundaries)。 - 从仓库根安装固定版本:
npm install -E zustand@5.0.15(多包时在 app workspace 用-w apps/<app>执行)。 - 复制
templates/src/stores/(先不含 session/)、templates/contracts/,将 Tasks 域替换为应用领域;在AppRoot的startApp中、数据层和错误接收器之后接入createStores;更新test/test-app.tsx;填写contracts/stores.json。 - 新增 action 时,先在
<name>-reducer-cases.ts添加会失败的用例,再添加 union 成员与 reducer case,最后按规则 2 的顺序添加 store 方法。 - 新增读取时,添加或复用选择器;构造值经
useShallow读取;仅在事件处理器中修改状态。 - 跨 store 写入时,在
crossStoreWrites中登记,通过updateAndPublish调用,并为每个持有行的新 store 添加 reload 和updateAndPublish中的一行。 - 撤销功能:复制
templates/src/stores/session/与templates/test/stores/;feature 在useState初始化器中为每次会话创建一个 session store,持久化回调通过仓库写入toSavedSession(...);用restoreSession恢复(返回暂停状态)。 - 运行
npx jest src/stores --ci --maxWorkers=2与npm run typecheck。 - 修改持久化 store 的写入顺序后,在指定设备的 Release 构建上杀掉并重启应用,验证屏幕显示内容仍在(iOS 用
xcrun simctl terminate/launch;Android 用adb shell am force-stop/monkey)。 - 从仓库根运行两个检查脚本,修复所有 FAIL 行直至输出
RESULT: PASS。
适用场景
- 在 React Native/Expo 应用中引入 Zustand 5 做客户端状态管理。
- 需要“先写盘再显示”的一致性保证,避免后台被杀导致未保存数据丢失。
- 从 Redux、Context、MobX 或 persist+AsyncStorage 迁移到 Zustand。
- 需要可撤销会话(命令日志 + 快照恢复)。
- 希望在 CI 中通过静态检查防止 store 规则漂移。
限制与注意事项
- 资料未提供该技能的收费、价格或商业授权信息,暂未提供。
- 规则严格绑定 React Native/Expo、Zustand 5.0.15、TanStack Query、
src/core/result.ts等前置结构;非该技术栈项目需要自行调整。 - 检查脚本依赖模板与示例的目录结构,新增规则需同步更新
scripts/selftest.mjs与测试夹具。 - 资料未说明浏览器端或非 React Native 平台的运行效果,暂未提供。
SKIP src/stores/settings/settings-reducer-cases.ts是预期跳过项,仅出现在尚未添加该用例文件的应用中。
反模式(资料列举)
模块级 create 单例、先 set 后写、忽略写入结果、选择器返回新对象、useTasksStore() 读取全部、跨 store 裸写只 reload 一个、useEffect 将 query.data 复制进 store、异步 store action 先 fetch 再 set、reducer 中使用 Date.now()/Math.random()/randomUUID()、事件式 action 名(task-added)、直接 push 状态数组、仅发布型 Premium store、撤销历史存盘、浮动安装(@latest 或 caret 范围)。