Skip to content
雲里
里雾

Zustand 中的状态机模式 — createTrainingStore 工厂与双层解耦

mindgym 开发 更新于 2026/5/19

Zustand 是一个”普通对象 + setState”的状态库,没有内置状态机概念。但 MindGym 的 5 个训练模块共享同一套生命周期(idle → countdown → playing → paused → complete),单靠普通 Zustand store 复制粘贴会导致严重的样板代码与不一致。本页拆解 createTrainingStore 工厂 + lifecycle hooks 如何把”phase 状态机”与”trial 状态机”分两层管理。


概述

MindGym 的训练模块(Schulte / Stroop / Flanker / CPT / N-Back)都有相同的 phase 生命周期。复制粘贴 5 份 phase 状态机的问题是:

createTrainingStore 工厂把共享 phase 状态机抽出来,每个模块通过 moduleInitialState / moduleActions / hooks 注入差异。这是”既轻量又显式”的折中方案。

工作方式

工厂函数签名

export function createTrainingStore<
  TModuleState extends object,
  TModuleActions extends object,
>(config: TrainingStoreConfig<TModuleState, TModuleActions>)

泛型让 TypeScript 编译期得到完整合并类型 TrainingBaseState & TModuleState & TrainingBaseActions & TModuleActions。工厂提供共享的 startCountdown / startPlaying / pause / resume / reset,模块只写差异。

双层 guard

两层互不渗透:工厂只管 phase 合法性,模块只管 trial 内部约束。加新模块不动 phase 状态机,加新 phase 不动模块。

Lifecycle hooks

hooks?: (set, get) => {
  onPause?: () => void
  onResume?: () => void
  onReset?: () => void
}

调用顺序:

reset 顺序之所以反过来是因为 hook 需要从 state 读 timer id 才能 clear——先 set 清空 state,hook 看到 null,timer 泄漏。

模块顶层闭包共享 timer 引用

CPT/N-Back 的 setTimeout 需要在 moduleActions 和 hooks 两个工厂之间共享。模块顶层就是天然的共享 scope:

let advanceTimer: ReturnType<typeof setTimeout> | null = null
let clearAdvanceFn: (() => void) | null = null
// moduleActions assign clearAdvanceFn, hooks 调用 clearAdvanceFn

教学上最少 TypeScript 知识门槛,单 store 单实例无并发问题。

Module Reset Registry

M4.2 引入。结果页”再来一次”按钮需要根据 record.module 字符串 reset 对应 store。早期硬编码 useSchulteSession.getState().reset() 导致 N-Back 结果页点”返回首页”实际 reset Schulte。

解法:

const moduleResetRegistry = new Map<TrainingModuleId, () => void>()

export function resetTrainingStore(moduleId: TrainingModuleId): void {
  moduleResetRegistry.get(moduleId)?.()
}

每次 createTrainingStore 调用注册自己的 reset 闭包。Map 的覆盖语义天然支持 Fast Refresh 重新注册。

与其他方案对比

方案Bundle学习曲线状态机可见性
createTrainingStore 工厂~80 行文档 + 类型
XState + useMachine+5KB极佳(可视化)
手写 reducer~30 行
单 store 复制粘贴00

教学项目首选工厂模式:bundle 几乎为零、TypeScript 完全约束、新模块只写差异。状态机的完整状态空间散落在 phase 字段 + 各模块 trial 状态里,靠文档维持心智模型。

面试问题

参见

参考