关于 xstate
面向 XState v5 开发者的实践指南,涵盖状态机、@xstate/store 及与 TanStack Query 的协作模式,并提供类型安全与 React 集成建议。
主要功能
- 01状态机与 Actor 建模
- 02@xstate/store 事件驱动状态管理
- 03XState 与 TanStack Query 协作
- 04React 集成模式
- 05常见问题与验证清单
适用场景
- 01构建多步骤应用流程
- 02实现轻量事件驱动状态
- 03协调数据获取与界面状态
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ pedronauck/ skills --skill xstate安装后在 Claude Code 中输入/xstate确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/machine-patterns.mdquery-patterns.mdstore-patterns.md详细介绍
XState v5 开发指南
本技能为在此项目中使用 XState v5 生态系统提供全面的指南、模式和最佳实践,包括:
- XState 状态机 - 用于复杂应用逻辑的有限状态机和 Actor
- @xstate/store - 用于轻量级状态管理的简单事件驱动型存储
- XState + TanStack Query - 数据获取编排的集成模式
快速参考
| 用例 | 工具 | 参考 |
|---|---|---|
| 复杂状态流、守卫、层级状态 | XState 状态机 | references/machine-patterns.md |
| 简单的事件驱动状态、原子、撤销/重做 | @xstate/store | references/store-patterns.md |
| 通过状态编排进行数据获取 | XState + TanStack Query | references/query-patterns.md |
核心原则
XState 状态机
- 显式状态:将应用逻辑建模为明确的有限状态和事件
- Actor 模型:在复杂编排中采用 Actor 模型
- 消除不可能的状态:使用状态图消除不可能的状态
- 声明式思维:以声明式方式思考状态转换
- 可视化:使用 Stately Editor 以可视化方式创建和调试状态机
@xstate/store
- 事件驱动更新:使用事件进行所有状态更新(绝不直接修改)
- 保持存储简单:专注于数据管理,而非复杂逻辑
- 优化订阅:利用选择器高效访问状态
- 响应式状态:使用原子构建可组合的响应式状态
- 升级路径:随着复杂度增加,迁移到 XState 状态机
XState + TanStack Query 集成
- 使用 XState 进行编排:使用 XState 状态机编排工作流
- 使用 TanStack Query 处理数据:使用 TanStack Query 获取和缓存数据
- Actor 作为桥梁:将 Query 操作封装为 XState Actor
- 在变更中更新缓存:将 Query 缓存更新放在变更回调中
XState 状态机
使用 setup() API 创建状态机
始终使用 setup() API 创建类型安全的状态机:
import { setup, createActor } from 'xstate';
export enum CounterEventType {
INCREMENT = 'increment',
DECREMENT = 'decrement',
}
const counterMachineSetup = setup({
types: {
context: {} as { count: number },
events: {} as
| { type: CounterEventType.INCREMENT }
| { type: CounterEventType.DECREMENT },
input: {} as { initialCount: number },
},
guards: {
isPositive: ({ context }) => context.count > 0,
},
});
// Define actions OUTSIDE setup using type-bound helpers
const incrementCount = counterMachineSetup.assign({
count: ({ context }) => context.count + 1,
});
export const counterMachine = counterMachineSetup.createMachine({
id: 'counter',
context: ({ input }) => ({ count: input.initialCount }),
initial: 'active',
states: {
active: {
on: {
[CounterEventType.INCREMENT]: {
actions: incrementCount,
},
},
},
},
});
React 集成
组件级状态机使用 useMachine(),覆盖操作使用 .provide():
import { useMachine } from '@xstate/react';
import { useMemo } from 'react';
function Counter({ onExternalAction }) {
const machine = useMemo(
() =>
counterMachine.provide({
actions: {
logEvent: () => onExternalAction(),
},
}),
[onExternalAction]
);
const [state, send] = useMachine(machine);
return (
<button onClick={() => send({ type: CounterEventType.INCREMENT })}>
Count: {state.context.count}
</button>
);
}
详细模式:请参阅 references/machine-patterns.md
@xstate/store
创建存储
通过明确的初始上下文和基于事件的转换来定义存储:
import { createStore } from '@xstate/store';
const userStore = createStore({
context: {
user: null as { id: string; name: string } | null,
isLoading: false,
error: null as string | null,
},
on: {
setUser: (context, event: { user: { id: string; name: string } }) => ({
...context, // Always spread to preserve other properties
user: event.user,
error: null,
}),
setLoading: (context, event: { isLoading: boolean }) => ({
...context,
isLoading: event.isLoading,
}),
},
});
// Use the trigger API for type-safe event sending
userStore.trigger.setUser({ user: { id: '1', name: 'John' } });
React 集成
import { useSelector, useStore } from '@xstate/store/react';
function Counter() {
const store = useStore({
context: { count: 0 },
on: {
increment: (context, event: { by: number }) => ({
...context,
count: context.count + event.by,
}),
},
});
const count = useSelector(store, (state) => state.context.count);
return <button onClick={() => store.trigger.increment({ by: 1 })}>{count}</button>;
}
撤销/重做模式
import { createStore } from '@xstate/store';
import { undoRedo } from '@xstate/store/undo';
const editorStore = createStore(
undoRedo({
context: { text: '' },
on: {
insertText: (context, event: { text: string }) => ({
...context,
text: context.text + event.text,
}),
},
})
);
editorStore.trigger.undo();
editorStore.trigger.redo();
详细模式:请参阅 references/store-patterns.md
XState + TanStack Query 集成
使用 QueryObserver 订阅查询
使用 fromCallback 和 QueryObserver 实现响应式订阅:
import { fromCallback } from "xstate";
import { QueryObserver, type QueryClient } from "@tanstack/react-query";
const subscribeToQuery = fromCallback(({ input, sendBack }) => {
const { queryClient, entityId } = input as {
queryClient: QueryClient;
entityId: string;
};
const observer = new QueryObserver(queryClient, {
queryKey: ["data", entityId],
queryFn: async () => { /* fetch logic */ },
});
const unsubscribe = observer.subscribe((result) => {
if (result.data) {
sendBack({ type: "REMOTE_UPDATE", data: result.data });
}
});
// Emit current result immediately
const currentResult = observer.getCurrentResult();
if (currentResult.data) {
sendBack({ type: "REMOTE_UPDATE", data: currentResult.data });
}
return () => unsubscribe();
});
使用 useMutation 执行变更
将 useMutation 钩子封装为 fromPromise Actor,并通过 .provide() 提供:
const machine = useMemo(
() =>
createDataMachine().provide({
actors: {
saveData: fromPromise(async ({ input }) => {
return await saveMutation.mutateAsync(input.data);
}),
},
}),
[saveMutation]
);
详细模式:请参阅 references/query-patterns.md
基本模式(适用于所有工具)
- 所有状态机都使用
setup()- 提供更出色的类型推断 - 事件类型使用枚举 - 遵循 UPPER_SNAKE_CASE 约定
- 在
setup()外定义操作 - 使用setup().assign()等类型绑定辅助函数 - 使用
enqueueActions()执行顺序操作 - 将相关操作组合在一起 - React 集成使用
.provide()- 使用外部方法覆盖操作 - 从
input初始化上下文 - 动态初始化时使用函数形式 - 存储中始终展开
...context- 保留其他属性 - 清理订阅 - 从回调 Actor 返回清理函数
何时使用各工具
以下情况使用 XState 状态机:
- 需要具有特定转换的有限状态
- 需要守卫和条件转换逻辑
- 需要调用 Actor 进行异步编排
- 需要层级状态或并行状态
- 构建复杂的多步骤工作流
使用 @xstate/store 的场景:
- 简单的全局或局部状态管理
- 无需复杂流程的事件驱动状态更新
- 需要撤销/重做功能
- 希望使用轻量级替代方案,并可在之后升级到 XState
使用 XState + TanStack Query 的场景:
- 需要协调数据获取与 UI 状态
- 构建具有缓存同步功能的实时特性
- 管理包含服务端状态的复杂异步工作流
应避免的常见问题
所有 XState 工具
- 不要使用泛型参数;请改用
setup() - 不要在
setup()中定义操作;请在外部使用类型绑定辅助函数定义操作 - 不要使用字符串字面量作为事件类型;请改用枚举
- 不要直接修改上下文;始终使用
assign()或返回新对象 - 不要让守卫函数具有副作用
- 不要使用已弃用的 v4 API(
interpret()、cond等) - 不要忘记对 Actor 调用
.start()
@xstate/store 特有事项
- 返回值中切勿忘记展开
...context - 切勿订阅后不清理
- 切勿将 store 用于复杂状态机(请迁移到 XState)
TanStack Query 集成
- 不要在回调 Actor 外创建 QueryObserver
- 不要在状态机操作中更新查询缓存
- 不要将变更钩子直接传给 Actor(请用
fromPromise包装) - 不要从闭包访问 QueryClient;请通过上下文传递
验证清单
完成任何涉及 XState 生态的任务前:
状态机
- 状态机使用
setup()API 确保类型安全 - 使用枚举定义事件类型
- 在
setup()外使用类型绑定辅助函数定义操作 - 使用函数形式从
input初始化上下文 - 守卫是没有副作用的纯函数
- React 组件使用
useMachine()或useActorRef()
@xstate/store
- 始终在转换返回值中展开
...context - 使用
triggerAPI 类型安全地发送事件 - 创建选择器以优化订阅
- 在 React 组件中清理订阅
TanStack Query 集成
- 使用
fromCallback包装 QueryObserver 订阅 - 使用
fromPromise包装变更,并通过.provide()提供 - 在变更回调中更新查询缓存,而非在状态机操作中更新
- 从回调 Actor 返回清理函数
- 通过状态机的 input/上下文传递 QueryClient
始终执行
- 运行类型检查(
pnpm run typecheck) - 运行测试(
pnpm run test)
详细参考文档
如需全面了解使用模式、示例和深入指南,请查阅以下参考文件:
references/machine-patterns.md- 完整的 XState 状态机使用模式,包括层级状态、并行状态、Actor、测试以及从 v4 迁移references/store-patterns.md- 完整的 @xstate/store 使用模式,包括选择器、原子、效果、撤销/重做,以及何时迁移到状态机references/query-patterns.md- 完整的 TanStack Query 集成模式,包括 QueryObserver、变更和完整的状态机示例
评论
支持 Markdown