react-state-machines

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

React State Machines with XState v5

基于XState v5的React状态机

Overview

概述

State machines make impossible states unrepresentable by modeling UI behavior as explicit states, transitions, and events. XState v5 (2.5M+ weekly npm downloads) unifies state machines with the actor model—every machine is an independent entity with its own lifecycle, enabling sophisticated composition patterns.
状态机通过将UI行为建模为明确的状态、转换和事件,使不可能的状态无法被表示。XState v5(每周npm下载量超过250万次)将状态机与Actor模型相结合——每个机器都是具有独立生命周期的独立实体,支持复杂的组合模式。

When to Use This Skill

何时使用该技能

Trigger patterns:
  • Boolean flag explosion: multiple
    isLoading
    ,
    isError
    ,
    isSuccess
    flags
  • Implicit states: writing
    if (isLoading && !isError && data)
    to derive mode
  • Defensive coding: guards before state updates to prevent invalid transitions
  • Timing coordination: timeouts, delays, debouncing across states
  • State dependencies: one state depends on another to update correctly
Do not use for:
  • Simple boolean toggles with no async (useState is simpler)
  • Single form fields with basic validation (useReducer suffices)
  • Server state caching (React Query/TanStack Query handles this)
  • Static data transformations (useMemo is better)
  • Simple counters or toggles (useState is clearer)
See decision-trees.md for comprehensive decision guidance
触发场景:
  • 布尔标志泛滥:多个
    isLoading
    isError
    isSuccess
    标志
  • 隐式状态:通过编写
    if (isLoading && !isError && data)
    来推导模式
  • 防御性编码:在状态更新前添加守卫以防止无效转换
  • 时序协调:跨状态的超时、延迟、防抖处理
  • 状态依赖:一个状态依赖另一个状态才能正确更新
不适用场景:
  • 无异步逻辑的简单布尔切换(使用useState更简单)
  • 基础验证的单个表单字段(useReducer足够)
  • 服务器状态缓存(React Query/TanStack Query可处理此场景)
  • 静态数据转换(useMemo更合适)
  • 简单计数器或切换器(useState更清晰)
详见decision-trees.md获取全面的决策指导

Core Mental Model

核心思维模型

Finite states represent modes of behavior:
idle
,
loading
,
success
,
error
. A component can only be in ONE state at a time.
Context (extended state) stores quantitative data that doesn't define distinct states. The finite state says "playing"; context says what at what volume.
Events trigger transitions between states. Events are objects:
{ type: 'SUBMIT', data: formData }
.
Guards conditionally allow/block transitions:
{ guard: 'hasValidInput' }
.
Actions are fire-and-forget side effects during transitions or state entry/exit.
Invoked actors are long-running processes (API calls, subscriptions) with lifecycle management and cleanup.
有限状态代表行为模式:
idle
loading
success
error
。组件同一时间只能处于一种状态。
上下文(扩展状态)存储不定义不同状态的定量数据。有限状态表示“正在播放”;上下文则说明播放的内容音量
事件触发状态之间的转换。事件是对象:
{ type: 'SUBMIT', data: formData }
守卫有条件地允许/阻止转换:
{ guard: 'hasValidInput' }
动作是转换期间或状态进入/退出时的一次性副作用。
调用的Actor是具有生命周期管理和清理机制的长期运行进程(API调用、订阅)。

Quick Start: XState v5 setup() Pattern

快速开始:XState v5 setup() 模式

typescript
import { setup, assign, fromPromise } from 'xstate';

const fetchMachine = setup({
  types: {
    context: {} as { data: User | null; error: string | null },
    events: {} as 
      | { type: 'FETCH'; userId: string }
      | { type: 'RETRY' }
  },
  actors: {
    fetchUser: fromPromise(async ({ input, signal }) => {
      const res = await fetch(`/api/users/${input.userId}`, { signal });
      if (!res.ok) throw new Error(res.statusText);
      return res.json();
    })
  },
  actions: {
    setData: assign({ data: ({ event }) => event.output }),
    setError: assign({ error: ({ event }) => event.error.message })
  }
}).createMachine({
  id: 'fetch',
  initial: 'idle',
  context: { data: null, error: null },
  states: {
    idle: { on: { FETCH: 'loading' } },
    loading: {
      invoke: {
        src: 'fetchUser',
        input: ({ event }) => ({ userId: event.userId }),
        onDone: { target: 'success', actions: 'setData' },
        onError: { target: 'failure', actions: 'setError' }
      }
    },
    success: { on: { FETCH: 'loading' } },
    failure: { on: { RETRY: 'loading' } }
  }
});
typescript
import { setup, assign, fromPromise } from 'xstate';

const fetchMachine = setup({
  types: {
    context: {} as { data: User | null; error: string | null },
    events: {} as 
      | { type: 'FETCH'; userId: string }
      | { type: 'RETRY' }
  },
  actors: {
    fetchUser: fromPromise(async ({ input, signal }) => {
      const res = await fetch(`/api/users/${input.userId}`, { signal });
      if (!res.ok) throw new Error(res.statusText);
      return res.json();
    })
  },
  actions: {
    setData: assign({ data: ({ event }) => event.output }),
    setError: assign({ error: ({ event }) => event.error.message })
  }
}).createMachine({
  id: 'fetch',
  initial: 'idle',
  context: { data: null, error: null },
  states: {
    idle: { on: { FETCH: 'loading' } },
    loading: {
      invoke: {
        src: 'fetchUser',
        input: ({ event }) => ({ userId: event.userId }),
        onDone: { target: 'success', actions: 'setData' },
        onError: { target: 'failure', actions: 'setError' }
      }
    },
    success: { on: { FETCH: 'loading' } },
    failure: { on: { RETRY: 'loading' } }
  }
});

React Integration Decision Tree

React集成决策树

Use CaseHookWhy
Simple component state
useMachine
Straightforward, re-renders on all changes
Performance-critical
useActorRef
+
useSelector
Selective re-renders only
Global/shared state
createActorContext
React Context integration
Basic pattern:
typescript
import { useMachine } from '@xstate/react';

function Toggle() {
  const [snapshot, send] = useMachine(toggleMachine);
  return (
    <button onClick={() => send({ type: 'TOGGLE' })}>
      {snapshot.matches('inactive') ? 'Off' : 'On'}
    </button>
  );
}
Performance pattern:
typescript
import { useActorRef, useSelector } from '@xstate/react';

const selectCount = (s) => s.context.count;
const selectLoading = (s) => s.matches('loading');

function Counter() {
  const actorRef = useActorRef(counterMachine);
  const count = useSelector(actorRef, selectCount);
  const loading = useSelector(actorRef, selectLoading);
  // Only re-renders when count or loading changes
}
使用场景Hook原因
简单组件状态
useMachine
直观易懂,所有变化都会触发重渲染
性能敏感场景
useActorRef
+
useSelector
仅在指定状态变化时触发重渲染
全局/共享状态
createActorContext
与React Context集成
基础模式:
typescript
import { useMachine } from '@xstate/react';

function Toggle() {
  const [snapshot, send] = useMachine(toggleMachine);
  return (
    <button onClick={() => send({ type: 'TOGGLE' })}>
      {snapshot.matches('inactive') ? 'Off' : 'On'}
    </button>
  );
}
性能优化模式:
typescript
import { useActorRef, useSelector } from '@xstate/react';

const selectCount = (s) => s.context.count;
const selectLoading = (s) => s.matches('loading');

function Counter() {
  const actorRef = useActorRef(counterMachine);
  const count = useSelector(actorRef, selectCount);
  const loading = useSelector(actorRef, selectLoading);
  // 仅在count或loading变化时触发重渲染
}

Anti-Patterns to Avoid

需避免的反模式

State explosion: Flat states for orthogonal concerns. Use parallel states instead.
Sending events from actions: Never
send()
inside
assign
. Use
raise
for internal events.
Impure guards: Guards must be pure—no side effects, no external mutations.
Subscribing to entire state: Use focused selectors with
useSelector
.
Not memoizing model:
typescript
// WRONG
const model = Model.fromJson(layout);  // New model every render

// CORRECT
const modelRef = useRef(Model.fromJson(layout));
状态泛滥:为正交关注点使用扁平状态。应改用并行状态。
从动作中发送事件:切勿在
assign
内使用
send()
。使用
raise
处理内部事件。
不纯守卫:守卫必须是纯函数——无副作用,无外部变更。
订阅整个状态:使用
useSelector
的聚焦选择器。
未记忆模型
typescript
// 错误写法
const model = Model.fromJson(layout);  // 每次渲染都会创建新模型

// 正确写法
const modelRef = useRef(Model.fromJson(layout));

Navigation to References

参考文档导航

Core Patterns

核心模式

  • xstate-v5-patterns.md: Complete v5 API, statecharts (hierarchy/parallel/history), promise actors
  • react-integration.md: useMachine vs useActorRef, Context patterns, side effect handling
  • testing-patterns.md: Unit testing, mocking actors, visualization debugging
  • xstate-v5-patterns.md:完整v5 API、状态图(层级/并行/历史)、Promise Actor
  • react-integration.md:useMachine与useActorRef对比、Context模式、副作用处理
  • testing-patterns.md:单元测试、Actor模拟、可视化调试

Decision Making & Best Practices

决策与最佳实践

  • decision-trees.md: When to use state machines vs useState/useReducer/React Query, machine splitting strategies
  • real-world-patterns.md: Complete examples - auth flows, file uploads, wizards, undo/redo, shopping carts
  • error-handling.md: Error boundaries, retry strategies, circuit breakers, graceful degradation
  • performance.md: Selector memoization, React.memo integration, machine splitting for performance
  • decision-trees.md:状态机与useState/useReducer/React Query的选型对比、机器拆分策略
  • real-world-patterns.md:完整实战示例 - 认证流程、文件上传、向导页、撤销/重做、购物车
  • error-handling.md:错误边界、重试策略、断路器、优雅降级
  • performance.md:选择器记忆化、React.memo集成、性能导向的机器拆分

Advanced Topics

进阶主题

  • persistence-hydration.md: localStorage persistence, SSR/Next.js hydration, snapshot serialization
  • migration-guide.md: Step-by-step migration from useState/useReducer with before/after examples
  • composition-patterns.md: Actor communication, machine composition, higher-order machines, systemId
  • skills-architecture.md: Input/output parameterization, library structure
  • persistence-hydration.md:localStorage持久化、SSR/Next.js hydration、快照序列化
  • migration-guide.md:从useState/useReducer迁移的分步指南,含前后对比示例
  • composition-patterns.md:Actor通信、机器组合、高阶机器、systemId
  • skills-architecture.md:输入/输出参数化、库结构

Key Reminders

关键提示

  1. setup() is the v5 way: Strong TypeScript inference, actor registration, action definitions
  2. Invoke for async, actions for sync: Actions are fire-and-forget; invoked actors have lifecycle
  3. Finite states for modes, context for data: Don't create states for every data variation
  4. Visualize first: Stately Studio (stately.ai/editor) makes machines living documentation
  1. setup()是v5的标准方式:强大的TypeScript类型推断、Actor注册、动作定义
  2. 异步逻辑用Invoke,同步逻辑用Actions:Actions是一次性执行;被调用的Actor具有生命周期
  3. 有限状态表示模式,上下文存储数据:不要为每个数据变体创建状态
  4. 先可视化:Stately Studio(stately.ai/editor)可将机器转化为可维护的文档

Red Flags

危险信号

  • More than 3-4 boolean flags → Need state machine
  • Writing
    if (a && !b && c)
    to determine mode → States should be explicit
  • Bugs from invalid state combinations → Machine prevents impossible states
  • Can't explain state transitions to stakeholders → Visualization solves this
  • 超过3-4个布尔标志 → 需要状态机
  • 通过编写
    if (a && !b && c)
    来确定模式 → 状态应显式定义
  • 无效状态组合导致的Bug → 状态机可防止不可能的状态出现
  • 无法向利益相关者解释状态转换 → 可视化可解决此问题

Related Skills

相关技能

  • react: Parent skill for React patterns
  • nextjs: Server/client state coordination
  • test-driven-development: Test machines with createActor before UI integration
  • react:React模式的父技能
  • nextjs:服务端/客户端状态协调
  • test-driven-development:在UI集成前使用createActor测试机器