xstate

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

XState v5 Developer Guide

XState v5开发者指南

This skill provides comprehensive guidelines, patterns, and best practices for working with the XState v5 ecosystem in this project, including:
  • XState State Machines - Finite state machines and actors for complex application logic
  • @xstate/store - Simple event-driven stores for lightweight state management
  • XState + TanStack Query - Integration patterns for data fetching orchestration
本指南提供了在项目中使用XState v5生态系统的综合指引、模式和最佳实践,涵盖:
  • XState状态机 - 用于处理复杂应用逻辑的有限状态机和actor
  • @xstate/store - 用于轻量级状态管理的简单事件驱动存储
  • XState + TanStack Query - 用于数据获取编排的集成模式

Quick Reference

快速参考

Use CaseToolReference
Complex state flows, guards, hierarchical statesXState Machines
references/machine-patterns.md
Simple event-driven state, atoms, undo/redo@xstate/store
references/store-patterns.md
Data fetching with state orchestrationXState + TanStack Query
references/query-patterns.md
使用场景工具参考文档
复杂状态流、守卫、层级状态XState Machines
references/machine-patterns.md
简单事件驱动状态、原子状态、撤销/重做@xstate/store
references/store-patterns.md
带状态编排的数据获取XState + TanStack Query
references/query-patterns.md

Core Principles

核心原则

XState State Machines

XState状态机

  • Explicit States: Model application logic as explicit finite states and events
  • Actor Model: Embrace the actor model for complex orchestration
  • Impossible States: Use statecharts to eliminate impossible states
  • Declarative Thinking: Think declaratively about state transitions
  • Visualization: Use Stately Editor for visual machine creation and debugging
  • 显式状态:将应用逻辑建模为显式的有限状态和事件
  • Actor模型:采用Actor模型实现复杂编排
  • 消除不可能状态:使用状态图消除不可能存在的状态
  • 声明式思维:以声明式的思路设计状态转换
  • 可视化:使用Stately Editor可视化创建和调试状态机

@xstate/store

@xstate/store

  • Event-Driven Updates: Use events for all state updates (never direct mutations)
  • Keep Stores Simple: Focus on data management, not complex logic
  • Optimized Subscriptions: Leverage selectors for efficient state access
  • Reactive State: Use atoms for composable, reactive state
  • Upgrade Path: Migrate to XState machines when complexity grows
  • 事件驱动更新:所有状态更新都通过事件实现(禁止直接修改状态)
  • 保持存储简单:聚焦数据管理,不要承载复杂逻辑
  • 优化订阅:利用选择器实现高效的状态访问
  • 响应式状态:使用原子状态实现可组合的响应式状态
  • 升级路径:复杂度提升时可迁移到XState状态机

XState + TanStack Query Integration

XState + TanStack Query集成

  • XState for Orchestration: Use XState machines for workflow orchestration
  • TanStack Query for Data: Use TanStack Query for data fetching and caching
  • Actors as Bridges: Wrap Query operations as XState actors
  • Cache Updates in Mutations: Keep query cache updates in mutation callbacks

  • XState负责编排:使用XState状态机实现工作流编排
  • TanStack Query负责数据:使用TanStack Query实现数据获取和缓存
  • Actor作为桥梁:将Query操作封装为XState Actor
  • 变更中更新缓存:将查询缓存的更新逻辑放在变更回调中

XState State Machines

XState状态机

Creating a Machine with setup() API

使用setup() API创建状态机

Always use the
setup()
API for type-safe machines:
typescript
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,
        },
      },
    },
  },
});
始终使用
setup()
API创建类型安全的状态机:
typescript
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 Integration

React集成

Use
useMachine()
for component-scoped machines and
.provide()
for action overrides:
typescript
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>
  );
}
For detailed patterns: See
references/machine-patterns.md

针对组件级状态机使用
useMachine()
,通过
.provide()
覆盖动作:
typescript
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

@xstate/store

Creating a Store

创建存储

Define stores with clear initial context and event-based transitions:
typescript
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' } });
定义存储时需明确初始上下文和基于事件的转换:
typescript
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 Integration

React集成

typescript
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>;
}
typescript
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>;
}

Undo/Redo Pattern

撤销/重做模式

typescript
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();
For detailed patterns: See
references/store-patterns.md

typescript
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 Integration

XState + TanStack Query集成

Query Subscriptions with QueryObserver

使用QueryObserver实现查询订阅

Use
fromCallback
with
QueryObserver
for reactive subscriptions:
typescript
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();
});
结合
fromCallback
QueryObserver
实现响应式订阅:
typescript
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();
});

Mutations with useMutation

结合useMutation实现变更

Wrap
useMutation
hooks as
fromPromise
actors and provide via
.provide()
:
typescript
const machine = useMemo(
  () =>
    createDataMachine().provide({
      actors: {
        saveData: fromPromise(async ({ input }) => {
          return await saveMutation.mutateAsync(input.data);
        }),
      },
    }),
  [saveMutation]
);
For detailed patterns: See
references/query-patterns.md

useMutation
钩子封装为
fromPromise
Actor,通过
.provide()
注入:
typescript
const machine = useMemo(
  () =>
    createDataMachine().provide({
      actors: {
        saveData: fromPromise(async ({ input }) => {
          return await saveMutation.mutateAsync(input.data);
        }),
      },
    }),
  [saveMutation]
);
详细模式请参考
references/query-patterns.md

Essential Patterns (All Tools)

核心模式(所有工具通用)

  1. Use
    setup()
    for all machines
    - provides superior type inference
  2. Use enums for event types - UPPER_SNAKE_CASE convention
  3. Define actions outside
    setup()
    - use type-bound helpers like
    setup().assign()
  4. Use
    enqueueActions()
    for sequential actions
    - group related actions together
  5. Use
    .provide()
    for React integration
    - override actions with external methods
  6. Initialize context from
    input
    - use function form for dynamic initialization
  7. Always spread
    ...context
    in stores
    - preserve other properties
  8. Clean up subscriptions - return cleanup functions from callback actors

  1. 所有状态机都使用
    setup()
    - 提供更优秀的类型推导能力
  2. 事件类型使用枚举定义 - 遵循大写下划线命名规范
  3. setup()
    外部定义动作
    - 使用类型绑定的辅助方法如
    setup().assign()
  4. 顺序动作使用
    enqueueActions()
    - 将相关动作分组
  5. React集成使用
    .provide()
    - 用外部方法覆盖动作
  6. input
    初始化上下文
    - 使用函数形式实现动态初始化
  7. 存储中始终展开
    ...context
    - 保留其他属性
  8. 清理订阅 - 从回调Actor中返回清理函数

When to Use Each Tool

各工具适用场景

Use XState State Machines When:

适合使用XState状态机的场景:

  • Need finite states with specific transitions
  • Need guards and conditional transition logic
  • Need invoked actors for async orchestration
  • Need hierarchical or parallel states
  • Building complex multi-step workflows
  • 需要具备明确转换规则的有限状态
  • 需要守卫和条件转换逻辑
  • 需要调用Actor实现异步编排
  • 需要层级或并行状态
  • 构建复杂的多步工作流

Use @xstate/store When:

适合使用@xstate/store的场景:

  • Simple global or local state management
  • Event-driven state updates without complex flows
  • Need undo/redo functionality
  • Want a lightweight alternative with upgrade path to XState
  • 简单的全局或本地状态管理
  • 无复杂流程的事件驱动状态更新
  • 需要撤销/重做功能
  • 需要轻量级方案,且未来可升级到XState

Use XState + TanStack Query When:

适合使用XState + TanStack Query的场景:

  • Need to orchestrate data fetching with UI state
  • Building real-time features with cache synchronization
  • Managing complex async workflows with server state

  • 需要结合UI状态编排数据获取逻辑
  • 构建带缓存同步的实时功能
  • 管理涉及服务端状态的复杂异步工作流

Common Pitfalls to Avoid

需要避免的常见陷阱

All XState Tools

所有XState工具通用

  • Don't use generic parameters; use
    setup()
    instead
  • Don't define actions inside
    setup()
    ; define them outside using type-bound helpers
  • Don't use string literals for event types; use enums instead
  • Don't mutate context directly; always use
    assign()
    or return new objects
  • Don't make guards impure or with side effects
  • Don't use deprecated v4 APIs (
    interpret()
    ,
    cond
    , etc.)
  • Don't forget to
    .start()
    actors
  • 不要使用泛型参数,改用
    setup()
  • 不要在
    setup()
    内部定义动作,通过类型绑定辅助方法在外部定义
  • 不要用字符串字面量作为事件类型,改用枚举
  • 不要直接修改上下文,始终使用
    assign()
    或返回新对象
  • 不要编写非纯或带副作用的守卫
  • 不要使用已废弃的v4 API(
    interpret()
    cond
    等)
  • 不要忘记调用
    .start()
    启动Actor

@xstate/store Specific

@xstate/store专属

  • Never forget to spread
    ...context
    in returns
  • Never subscribe without cleanup
  • Never use stores for complex state machines (migrate to XState)
  • 永远不要忘记在返回值中展开
    ...context
  • 订阅必须配套清理逻辑
  • 不要用存储实现复杂状态机(应迁移到XState)

TanStack Query Integration

TanStack Query集成相关

  • Don't create QueryObserver outside callback actors
  • Don't update query cache inside machine actions
  • Don't pass mutation hooks directly to actors (wrap in
    fromPromise
    )
  • Don't access QueryClient from closure; pass through context

  • 不要在回调Actor外部创建QueryObserver
  • 不要在状态机动作中更新查询缓存
  • 不要直接将变更钩子传递给Actor(需用
    fromPromise
    封装)
  • 不要从闭包中访问QueryClient,应通过上下文传递

Validation Checklist

验证检查清单

Before finishing any task involving XState ecosystem:
完成任何涉及XState生态的任务前,请检查:

For State Machines

状态机检查项

  • Machine uses
    setup()
    API for type safety
  • Event types are defined using enums
  • Actions are defined outside
    setup()
    using type-bound helpers
  • Context is initialized from
    input
    using function form
  • Guards are pure functions without side effects
  • React components use
    useMachine()
    or
    useActorRef()
  • 状态机使用
    setup()
    API保证类型安全
  • 事件类型使用枚举定义
  • 动作在
    setup()
    外部通过类型绑定辅助方法定义
  • 上下文通过函数形式从
    input
    初始化
  • 守卫是无副作用的纯函数
  • React组件使用
    useMachine()
    useActorRef()

For @xstate/store

@xstate/store检查项

  • Always spread
    ...context
    in transition return values
  • Use
    trigger
    API for type-safe event sending
  • Create selectors for optimized subscriptions
  • Clean up subscriptions in React components
  • 转换返回值中始终展开
    ...context
  • 使用
    trigger
    API实现类型安全的事件发送
  • 定义选择器优化订阅
  • React组件中做好订阅清理

For TanStack Query Integration

TanStack Query集成检查项

  • QueryObserver subscriptions are wrapped in
    fromCallback
  • Mutations are wrapped in
    fromPromise
    and provided via
    .provide()
  • Query cache updates happen in mutation callbacks, not machine actions
  • Cleanup functions are returned from callback actors
  • QueryClient is passed through machine input/context
  • QueryObserver订阅封装在
    fromCallback
  • 变更通过
    fromPromise
    封装并通过
    .provide()
    注入
  • 查询缓存更新在变更回调中执行,而非状态机动作中
  • 回调Actor返回清理函数
  • QueryClient通过状态机的input/context传递

Always

通用检查项

  • Run type checks (
    pnpm run typecheck
    )
  • Run tests (
    pnpm run test
    )

  • 运行类型检查(
    pnpm run typecheck
  • 运行测试(
    pnpm run test

Detailed Reference Documentation

详细参考文档

For comprehensive patterns, examples, and in-depth guidance, consult these reference files:
  • references/machine-patterns.md
    - Complete XState machine patterns including hierarchical states, parallel states, actors, testing, and migration from v4
  • references/store-patterns.md
    - Full @xstate/store patterns including selectors, atoms, effects, undo/redo, and when to migrate to machines
  • references/query-patterns.md
    - Complete TanStack Query integration patterns with QueryObserver, mutations, and full machine examples
如需了解完整模式、示例和深度指引,请参考以下文档:
  • references/machine-patterns.md
    - 完整的XState状态机模式,涵盖层级状态、并行状态、Actor、测试以及v4迁移指南
  • references/store-patterns.md
    - 完整的@xstate/store模式,涵盖选择器、原子状态、副作用、撤销/重做以及何时迁移到状态机的指引
  • references/query-patterns.md
    - 完整的TanStack Query集成模式,涵盖QueryObserver、变更以及完整的状态机示例