关于 state-management
面向开发者的应用状态建模技能,适用于设计、审查和重构状态。它先明确最小事实来源与有效转换,再映射到具体库。
主要功能
- 01分类源状态、派生状态、服务端状态和 URL 状态
- 02定义不变量与有限状态
- 03将更新建模为事件和转换
- 04选择合适的状态存放位置
- 05映射到常见状态库
适用场景
- 01为新功能设计状态
- 02检查代码中的重复或无效状态
- 03在保持行为的同时重构状态
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ statelyai/ skills --skill state-management安装后在 Claude Code 中输入/state-management确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/evals.jsonreferences/async-state.mdno-library.mdreact.mdredux.mdreview-checklist.mdtypescript-modeling.mdvue-pinia.mdzustand.md详细介绍
状态管理
优先使用此技能进行状态建模,然后再映射到库。
目标不是推荐某个偏爱的库,而是构建一个可靠的状态模型,无论最终实现使用本地组件状态、reducer、Redux、Zustand、Pinia、URL 参数、TanStack Query,还是完全不使用库,都能保持稳健。
你的工作:
- 将状态缩减为最小且有效的事实来源
- 将派生值移至选择器、getter、计算属性或渲染时计算
- 当多个布尔值代表互斥模式时,用明确的有限状态替代布尔值混杂
- 通过事件和状态转换定义状态变化,而不是随意调用 setter
- 将每个状态放在合适的位置
- 检查现有代码中的无效状态、重复数据、同步错误和放置不当的副作用
- 只有在模型清晰后,才将其映射到用户选择的库
第一轮检查
在现有代码库中:
- 检查本地
package.json文件和导入语句。 - 阅读问题附近的组件、store、reducer 和选择器/getter 代码。
- 确定哪些状态是本地状态、全局状态、远程状态、URL 状态或外部状态。
- 在提出更改建议前,将当前实现细节与实际领域模型区分开来。
如果用户希望从零开始设计,则直接对领域建模,然后再选择实现方式。
任务模式
回答前选择一种模式:
- 设计:根据需求构建新的状态模型。
- 审查:找出错误、风险、无效状态和放置不当的状态。
- 重构:在保持行为不变的同时简化模型并强化不变量。
- 库映射:将已有的可靠模型转换为 React、Redux、Zustand、Vue/Pinia 或纯函数实现。
如果真正的问题是模型有缺陷,不要直接跳到 API 建议。
核心分类
在决定每个值的归属前,先对其进行分类。
- 源状态:应用必须存储的权威事实。
- 派生状态:可根据源状态和 props/输入计算得出的值。除非有充分的边界原因,否则不要存储这些值。
- 有限状态:互斥模式,例如
idle、editing、submitting、success、failure。 - 服务端状态:涉及加载、新鲜度、缓存和失效处理的远程数据。
- URL 状态:应在刷新后保留、支持分享或参与前进/后退导航的值。
- 临时可变值:计时器、DOM 句柄、
AbortController、之前的快照、订阅以及其他非视觉可变值。 - 历史状态:撤销/重做栈、审计日志、之前的状态和快照。
如果某个值无法明确归类,你可能还没有理解它的作用。
核心原则
- 保持源状态最少。
- 每个事实优先只使用一个事实来源。
- 计算派生值,而不是同步它们。
- 对互斥模式使用一个有限的
status字段或判别联合表示,而不是使用多个布尔值。 - 让无效状态难以表示,或无法表示。
- 将更新建模为领域事件和状态转换。
- 将副作用放在边界处。副作用应响应状态转换,而不是充当主要协调机制。
- 状态应尽可能放低,但在必要时也要放高。
- 区分客户端状态、服务端缓存和 URL 状态。
- 如果多个值总是一起变化,就明确建模它们之间的关系。
工作流
除非用户明确要求更窄的范围,否则请按此顺序进行。
- 列出所有当前或计划中的状态值。
- 使用上述分类法对每个值进行分类。
- 消除重复:
- 删除镜像属性
- 删除可推导出的值
- 除非有明确的缓存边界,否则不要同时存储对象及其选中的 ID
- 定义不变量:
- 哪些组合有效?
- 哪些组合不可能出现?
- 每种模式下必须始终满足什么条件?
- 识别有限模式:
- 如果多个布尔值实际描述的是同一种状态,请将它们合并为有限状态
- 列出领域事件:
- 可能发生什么?
- 由谁或什么触发?
- 事件携带哪些数据?
- 梳理状态转换:
- 每个事件在哪些状态下有效?
- 会发生什么变化?
- 在边界处会产生哪些副作用?
- 决定状态的存放位置:
- 本地组件状态
- reducer/store
- URL
- 服务端缓存/查询层
- ref 或外部可变对象
- 将模型映射到选定的库。
需求不明确时,先展示模型,再编写代码。
建模问题
编写或修改代码前,请先思考以下问题:
- 我们实际需要存储的最小事实是什么?
- 哪些值是这些事实的派生视图?
- 哪些模式互斥?
- 当前可以表示哪些不可能出现的状态?
- 可能发生哪些事件?每种状态下哪些事件有效?
- 页面导航或刷新后,这个状态是否需要保留?
- 这真的是应用状态,还是服务端缓存?
- 这个值是渲染所必需的,还是临时可变句柄?
- 谁拥有这个状态?谁有权更改它?
- 我们存储的是实现细节,还是领域事实?
- 是否在使用副作用同步本应派生出的状态?
选择表示方式
优先选择能保持不变量的最简单表示方式。
- 独立的本地值:只有字段确实相互独立时,才使用本地状态。
- 有限模式:使用字符串字面量状态或判别联合,而不是多个布尔值。
- 协调更新:多个字段需要同时更改,或事件处理取决于当前状态时,使用 reducer。
- 复杂工作流:存在守卫条件、重试、取消、并发或子进程时,使用状态机或 actor 模型。
- 关系型集合:嵌套更新变得繁琐或容易不一致时,对实体进行规范化。
- 派生读取:使用选择器、getter、计算值或渲染时计算。
- 服务端数据:优先使用查询/缓存工具,而不是将远程生命周期相关逻辑强行放入 UI store。
- 不参与渲染的可变数据:使用 refs 或外部可变对象。
除非更简单的抽象无法强制实施所需的不变量,否则不要建议使用更复杂的抽象。
状态存放位置指南
选择状态的存放位置时,请考虑:
- 谁会读取它?
- 谁会写入它?
- 它需要存续多久?
- 刷新后是否需要保留?
- 是否需要通过 URL 分享?
- 它是权威应用状态,还是远程缓存?
推荐的默认做法:
- 将临时的纯视图状态保留在本地
- 只有在多个使用方确实需要共享跨组件客户端状态时,才将其放入 reducer/store
- 将可分享的导航/筛选状态保存在 URL 中
- 将获取的数据保存在服务端状态/缓存层中
- 不要将非视觉句柄放入渲染状态
反模式
在评审和重构中,积极指出以下问题:
- 将派生状态存储起来,并通过副作用重新同步
- 在本地状态中镜像 props
- 用多个布尔值描述同一种模式
- 没有明确理由却同时存储
selectedItem和selectedItemId - 在组件、store 和 URL 中重复存储状态
- 描述 setter 而非领域事件的操作名称
- 应当规范化却使用深层嵌套的关系状态
- 用
useEffect链协调业务逻辑 - 没有充分理由,却将大部分表单状态放入全局 store
- 用一堆可空字段代替显式状态
- 将服务端状态、客户端状态和请求生命周期混在一个无法区分的对象中
参考资料路由
仅打开与当前任务相关的参考资料:
- React:
references/react.md - Redux 和 Redux Toolkit:
references/redux.md - Zustand:
references/zustand.md - Vue 和 Pinia:
references/vue-pinia.md - 原生 JavaScript 或与库无关的实现:
references/no-library.md - TypeScript 建模与穷尽性检查:
references/typescript-modeling.md - 异步生命周期建模:
references/async-state.md - 评审流程与代码异味检测:
references/review-checklist.md
如果问题明确涉及已使用 XState 或 Actor 系统的代码库,请优先使用专门的 xstate-v5 技能获取实现细节。
输出格式
建议采用以下回答结构:
- 状态清单:当前存在或必须存在的内容。
- 发现的问题或不变量:重复、无效状态和设计约束。
- 推荐模型:
- 源状态
- 派生状态
- 有限状态或判别联合
- 事件和转换
- 状态放置位置
- 库的映射:仅当用户要求针对特定技术栈实现时提供。
- 代码:仅在模型足够清晰后提供。
对于评审任务,先列出具体问题。对于设计任务,先介绍模型。
最终自检
回答前,请确认:
- 每个存储的值确实都需要存储
- 派生值没有被镜像为源状态
- 互斥模式没有被表示成互不相关的布尔值
- 无效状态已减少或消除
- 状态的所有权和放置位置明确
- 副作用没有代替选择器或状态转换来处理工作
- 异步状态已明确建模
- 推荐的抽象没有超出问题所需的复杂度
- 针对特定库的建议与用户实际使用的技术栈相符
评论
支持 Markdown