mcpField
X
xstate-v5

xstate-v5

github.com/statelyai开发48497 次安装23 周前更新
代码仓库

关于 xstate-v5

xstate-v5 是面向编码 Agent 的技能,用于设计、编写、审查和迁移 XState v5 状态机及 Actor 系统的 TypeScript 代码。它指导如何在 XState 与 @xstate/store 之间选择,并根据现有代码调整做法。

主要功能

  1. 01XState v5 状态机与 Actor 设计
  2. 02选择 XState 或 @xstate/store
  3. 03TypeScript 编写与代码审查
  4. 04局部迁移 v4 至 v5
  5. 05React、Vue、Svelte 和 Solid 集成

适用场景

  1. 01建模包含状态与转换的工作流
  2. 02审查或修复 XState 代码库
  3. 03将旧版 v4 模式迁移到 v5

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/statelyai/skills --skill xstate-v5

安装后在 Claude Code 中输入/xstate-v5确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

evals/
evals.json
extract_and_typecheck.py
regrade_weighted.py
references/
adapters.md
advanced-patterns.md
examples.md
observables-and-inspection.md
v4-to-v5-quick-ref.md

详细介绍

XState v5

优先将此技能用于状态机和状态图工程,其次再考虑 API 正确性。

此技能仅适用于 v5。如果示例、博客文章、解答或本地代码带有 v4 的痕迹,请将其转换为 v5,而不要混用不同版本。优先参考本地仓库代码和官方 v5 文档,而非泛泛的记忆。

你的任务:

  • 在 xstate 和 @xstate/store 之间做出选择
  • 根据杂乱的需求设计可靠的机器或 Actor 系统
  • 以一致的风格编写现代 XState v5 TypeScript 代码
  • 审查、修复或改进现有 XState 代码
  • 出现旧式 v4 模式时,将其迁移
  • 当问题不只是 assign(...) 加上 fromPromise(...) 时,选择合适的 Actor 类型和动作形式
  • 在需要时,将机器和 Actor 连接到 @xstate/react、@xstate/vue、@xstate/svelte 或 @xstate/solid

初步检查

在现有代码库中:

  1. 检查本地 package.json 文件和导入语句。
  2. 阅读附近的机器、存储、Actor 和适配器用法。
  3. 在决定采取多强硬的意见之前,先区分新代码、本地编辑和迁移工作。
  4. 默认保留周边代码的风格。

任务模式

编写代码前先选择一种模式:

  • 新代码:优先采用此技能中的现代 v5 模式。
  • 本地编辑:保留本地结构和命名,除非现有代码混乱、存在问题,或用户要求进行整理。
  • 迁移:优先采用最小且安全的 v5 转换。除非用户要求更广泛的规范化,否则保留结构和语义。

对于迁移和本地编辑,除非为确保正确性所必需、能显著降低本地复杂度,或用户明确要求,否则不要引入 setup(...)、命名实现、对象形式的守卫/动作、标签或 Actor 分解。

选择工具

当领域属于简单的事件驱动状态管理时,优先使用 @xstate/store:

  • 没有需要协调、编排或显式建模生命周期的重要有限模式
  • 没有调用的异步进程
  • 没有 Actor 间通信
  • 不需要状态机或状态图概念,例如带守卫的转换、延迟转换、并行状态或历史状态

如果不需要机器级编排,简单的获取或变更逻辑也可以使用 @xstate/store。

当领域具备以下一项或多项特征时,优先使用 XState:

  • 会影响行为或 UI 的有限模式
  • 异步工作流、重试、取消或后台进程
  • 多个相互作用的进程或子 Actor
  • 显式守卫、延迟、标签或转换规则
  • 需要建模业务流程,而不只是更新状态

如果问题简单到不需要机器,请直言,并推荐使用 @xstate/store。

工作流

需求模糊时:

  1. 先勾勒机器的结构。
  2. 为重要的状态、事件、上下文、Actor 和标签命名。
  3. 简要指出不确定之处或需要权衡的地方。
  4. 然后编写代码。

需求明确时,简短勾勒结构或省略此步骤,直接编写代码。

不要为了概念上的纯粹而过度重构现有代码。在重要之处改进模型,但若没有充分理由进行大幅重写,就保留可正常工作的结构。

建模问题

在编写或修改代码前,请先思考以下问题:

  • 有哪些有限状态?将模式放在状态中,而不是上下文中。
  • 上下文中应该包含什么?只保留持久数据,不要存放可推导的布尔值或重复的模式信息。
  • 有哪些领域事件?优先使用有意义的名称,通常以点号分隔,例如 form.submitted 或 order.confirmed。
  • 哪些情况应该使用守卫,哪些应该使用独立的分支状态?
  • 哪些副作用应该通过调用 actor 来执行,哪些应该作为转换动作?
  • 事件能否携带所需数据,而不是先将临时中转数据存入上下文,留待后续状态使用?
  • fromPromise(...) 真的是合适的 actor 吗?还是说这是一个回调/订阅协议,应该随着时间推移将事件发送回来?
  • 状态机是否应该向外围系统发送事件,而不是让消费者根据上下文推断所有信息?
  • 某个子级关注点是否应该作为生成/调用的 actor,而不是让父级变得更加复杂?
  • 父级真的需要这些额外的中间状态吗?还是应该由子 actor 负责管理这些内部细节?
  • 哪些状态需要使用标签来表示 'loading'、'error' 或 'dirty' 等 UI 语义?
  • UI 能否使用 snapshot.matches(...)、标签或 snapshot.can(...),而不是添加额外的布尔值?

首选的 v5 模式

对于新代码,除非本地代码库有充分理由不这样做,否则优先采用以下模式:

  • 仅使用 TypeScript。
  • 优先使用 setup({...}).createMachine({...})。
  • 在 setup(...) 中定义命名的 actions、guards、actors 和 delays。
  • 当能提升一致性时,优先使用 { target: 'next' } 这样的转换对象,而不是简写形式。
  • 当能保持结构一致时,即使只有一个 action,也优先使用数组。
  • 当意图可复用或有名称时,优先使用 { type: 'track' } 或 { type: 'isValid', params: ... } 这样的 action 和 guard 对象。
  • 优先使用 tags: [] 表示 UI 语义和跨领域的状态含义。
  • 优先采用面向领域的事件名称;如果本地命名约定已确立,则沿用该约定。
  • 在并行状态中,将转换保留在生命周期需要变化的区域或子状态内。避免使用区域根节点转换去指向同级区域,或使用 reenter: true,除非确实有意重置整个源区域。
  • 派生值优先使用普通函数,而不是存储在上下文中。
  • 如果某个状态只需将数据传递给下一步,优先使用事件负载,而不是临时通过上下文中转。
  • 优先通过 params 向命名的 actions 和 guards 传递数据,而不是直接访问 event。
  • 不要将 assign(...) 视为唯一的 action 模式。对于简单上下文更新以外的可复用类型化逻辑,可考虑使用 const machineSetup = setup(...) 对象中作用域限定于 setup 的辅助函数,例如 machineSetup.createAction(...)、machineSetup.enqueueActions(...) 和 machineSetup.emit(...);如果命名的 action 对象更适合该行为,也可以使用它们。
  • 当命名的 assign(...) action 更新多个上下文属性时,每个属性更新器共享相同的 params 类型。对所有更新字段使用一个一致的 params 对象,或将工作拆分为多个独立的命名 actions。不要在同一个赋值器中混用不兼容的 params 形状。
  • 当实现必须检查 event 且其类型不明显时,优先使用 assertEvent(...) 进行类型收窄。
  • 在示例和最终代码中避免使用 as any 和其他宽松类型断言,除非没有更合适的本地方案。
  • 有意选择 actor 逻辑:单个请求对应单个结果时优先使用 fromPromise(...);订阅、计时器、外部回调以及会随时间发送事件的多事件协议则优先使用 fromCallback(...)。
  • 当状态机需要直接通知外围系统时,可考虑使用 emit(...)。
  • 尽可能让父状态保持粗粒度。如果额外的中间状态主要是在建模实现细节,优先让子 actor 管理这些内部行为。
  • 如果展示多个文件,请确保它们完整接通。为引用的模块和组件提供实际的导入/导出,或者缩小示例范围。

保持事件命名一致。事件名称中的 . 很有用,也有明确含义,包括用于部分事件描述符和更清晰的领域分组。请参阅关于 事件和转换 以及 assertEvent(...) 的 TypeScript 类型收窄 的官方文档。

当任务涉及超出 assign(...) 的类型化 actions、回调 actor、发出的事件或持久化/恢复时,请参阅 references/examples.md 中的规范代码结构和 references/advanced-patterns.md;关于 fromObservable(...)、检查和浏览器检查器模式,请参阅 references/observables-and-inspection.md。

UI 集成

优先让机器驱动 UI 行为:

  • 使用 snapshot.matches(...) 检查有限模式
  • 使用标签进行语义检查,例如加载、保存或脏状态
  • 使用 snapshot.can(...) 判断当前事件是否有效
  • 避免堆叠多个手动布尔值,重复表达机器中的真实状态

如果组件拥有一个小型本地 actor,通常使用 useMachine(...) 就足够了。

如果 actor 是共享的、生命周期较长或对性能敏感:

  • 创建或获取一次 actor ref
  • 使用 useSelector(...) 读取部分状态
  • actor 在子树中共享时,使用 actor context/provider 辅助工具

简明适配器指南请参阅 references/adapters.md。

审查与修复指南

审查或修复 XState 代码时,请留意:

  • 将有限模式存储在 context 中,而不是状态中
  • 本应拆分为子 actor 的大型机器
  • 隐藏在随机回调中或混入 assigner 的副作用
  • 在同一机器中混用 v4 和 v5 的概念
  • 描述 UI 操作机制而非领域含义的状态名称和事件名称
  • 应由 matches(...)、标签或选择器替代的多余布尔值
  • 简单问题中错过了使用 @xstate/store 的机会
  • 命名的 assign(...) 动作,其属性更新器隐式期望不同形状的 params
  • 并行区域的根级转换指向同级区域或使用 reenter: true;在 v5 中,这可能重新进入源区域,将其重置为初始子状态,并取消正在执行的 invoke

避免过度拆分。Actor 边界只有在能提升清晰度、归属关系或并发性时才有用。不要为了拆分而拆分。

迁移

存在旧代码时,除非用户要求进行更大范围的迁移,否则应逐步、局部地将代码迁移到 v5。

常见迁移目标包括:

  • cond -> guard
  • schema -> types
  • services -> actors
  • interpret(...) -> createActor(...)
  • 旧函数签名 -> 解构后的 v5 参数,例如 ({ context, event })

对于迁移任务:

  • 优先保留局部结构
  • 只更改正确性、兼容性或清晰度所必需的内容
  • 除非用户要求,否则不要统一改成完整的首选代码风格
  • 说明你选择进行的任何非局部重构
  • 不要仅因附近代码中存在无效的 hook 选项写法就予以保留
  • 迁移 React 用法时,应优先使用当前有效的 hook 接口,而不是试图将旧式 interpret 实现覆盖选项塞进 useMachine(...)
  • 将 v4 代码形态迁移到 v5 前,检查 type: 'parallel' 的区域根级转换;将处理器移到所属子状态/区域,或仅在确实需要更广泛地重新进入时,才由父级协调

快速迁移模式请参阅 references/v4-to-v5-quick-ref.md。

持久化

涉及持久化或水合时:

  • 持久化快照,而不只是 context,除非仅恢复 context 确实足够
  • 保存机器状态时优先使用 actor.getPersistedSnapshot()
  • 尽可能使用 createActor(machine, { snapshot }) 或适配器/provider 的快照选项进行水合
  • 不要通过猜测状态值并拼凑部分 context 数据来手动恢复

具体的持久化和水合模式请参阅 references/advanced-patterns.md。

测试

除非用户询问测试或要求编写测试,否则不要引入测试指南。

如果用户提出此类需求,请简要说明:

  • 测试关键转换和守卫
  • 测试 actor 行为的成功路径和失败路径
  • 根据机器状态测试 UI,而不是重复实现有状态逻辑
  • 适时提及基于模型的测试工具

如果需要链接,可指向 XState 的测试和图/路径生成文档。

可选工具

Stately Studio 和检查工具有助于设计、调试和沟通,但它们是可选的。当用户正在设计机器、想要可视化效果或需要更清晰的调试信息时,可以提及这些工具。

输出格式

优先采用以下输出形式:

  1. 需求不明确时,先简要勾勒机器。
  2. 代码。
  3. 简要说明状态、事件、上下文、actor 和标签。
  4. 仅在相关时提供迁移说明。

优先提供较少但完整的文件,而不是较多但只部分接通的示例。不要勾勒额外的 shell 文件,除非它们已完全接通。

请参阅 references/examples.md,获取更规范、且可随时间扩展的示例。

最终自检

回答前,快速检查代码是否具备编译条件:

  • 来自其他文件的每个 JSX 组件都有实际导入
  • 每个导入的符号都由所示文件实际导出
  • 每个 send(...) 和 snapshot.can(...) 调用都使用有效的事件对象
  • 异步工作使用 actors/fromPromise 和 invoke.input,而不是旧版结构
  • 如果引用提示词或周边系统中的外部辅助函数,请声明其签名,或提供足够的类型定义,让示例能够独立成立
  • 迁移示例不会为了保留无用的本地代码,而将无效的实现覆盖对象传入钩子
  • 旨在可运行的代码围栏中没有占位 shell 文件、遗漏的导入、... 或伪代码

如果完整的多文件答案变得过于冗长,请移除 shell 文件,保留仍能正确展示该模式的较小文件集。

评论

支持 Markdown