Skip to content

State machines

状态机就是用一种“可预测、可推导”的方式,描述一个东西在不同状态下,遇到不同事件会发生什么变化

为什么要用状态机

ts
const step = ref(1)
const loading = ref(false)
const disabled = computed(() => step.value === 3 && loading.value)

典型Vue痛点:

  • 状态是隐式的
  • 组合一多就开始失控
  • 很难回答一句话:现在系统到底处于什么状态?

创建 machines

ts
import { createMachine } from 'xstate';

const feedbackMachine = createMachine({
  id: 'feedback',
  initial: 'question',
  states: {
    question: {
      on: {
        'feedback.good': {
          target: 'thanks',
        },
      },
    },
    thanks: {
      // ...
    },
    // ...
  },
});

“在 question 状态下,如果收到 feedback.good 事件,就跳到 thanks

概念含义
initial初始状态
states所有可能的状态
on当前状态能接收的事件(会发生的事件)
target事件发生后跳转到的状态(事件一旦发生要么停留原地,要么导致状态的更改)

创建 actor

Machine 不会自己跑,要变成 Actor

ts
const feedbackActor = createActor(feedbackMachine)//用规则说明书,创建一个运行中的实例

feedbackActor.start()//状态从 initial 开始
feedbackActor.send({ type: 'feedback.good' })//发送事件,推动状态流转
对比MachineActor
是否运行❌ 不运行✅ 运行
是否可复用✅ 可复用❌ 一次实例
职责描述规则执行规则
类比类定义类实例

提供 implementations

类型约束

ts
const machineSetup = setup({
  types: {
    context: {} as { count: number; name: string },
    events: {} as
      | { type: 'increment'; value: number }
      | { type: 'reset' },
  },
})
ts
const incrementCount = machineSetup.assign({
  count: ({ context }) => context.count + 1,
})

Transitioning state (状态推导)

在不启动 actor 的情况下,纯计算“下一个状态会是什么”。这是 v5 推荐使用的方式(替代旧的 snapshot API)

Transitioning state =给定「当前状态 + 一个事件」,直接算出「下一个状态 + 会执行哪些 actions」

运行状态机会:

  • 真正运行
  • 会执行 actions
  • 会产生副作用

但有些场景你不想“运行”,比如:

  • 🔍 调试 / 可视化
  • 🧪 单元测试
  • 🔄 状态回放 / 时间旅行
  • 🧠 根据规则“预测”下一步

计算初始状态:

ts
const [initialState, initialActions] = initialTransition(machine)
  • machine:状态机

计算一次 transition:

ts
const [nextState, actions] =transition(machine, state, event)
  • machine:状态机
  • state:状态
  • event:事件
  • return:
    • nextState:下一状态的快照
ts
import { createMachine, initialTransition, transition } from 'xstate';

const machine = createMachine({
  initial: 'pending',
  states: {
    pending: {
      on: {
        start: { target: 'started' },
      },
    },
    started: {
      entry: 'doSomething',
    },
  },
});

const [initialState, initialActions] = initialTransition(machine);

console.log(initialState.value);
// logs 'pending'

console.log(initialActions);
// logs []

const [nextState, actions] = transition(machine, initialState, {
  type: 'start',
});

console.log(nextState.value);
// logs 'started'

console.log(actions);
// logs [{ type: 'doSomething', … }]

final

type: 'final' 的状态,不能再写 target,也不会再发生状态切换

一旦进入 final

  • 状态机 立刻完成
  • 向父级发送 done 事件(如果有父)
  • 当前状态机 生命周期结束

input

状态机在被actor使用时,可以接收input初始化状态机,这类比于调用函数传入形参

ts
import { createActor, setup } from 'xstate';

const feedbackMachine = setup({
  types: {
    context: {} as {
      userId: string;
      feedback: string;
      rating: number;
    },
    input: {} as {
      userId: string;
      defaultRating: number;
    },
  },
}).createMachine({
  context: ({ input }) => ({
    userId: input.userId,
    feedback: '',
    rating: input.defaultRating,
  }),
  // ...
});

const feedbackActor = createActor(feedbackMachine, {
  input: {
    userId: '123',
    defaultRating: 5,
  },
});

output

❌ 大多数情况下,用 createMachine 得到的 actor「不需要 output」✅ 只有当它“作为子 actor,被别人 invoke / spawn,并且需要把结果交出去”时,才有必要定义 output

ts
success: {
  type: 'final',
  meta: { title: '成功' },
  output: ({ context }) => ({
    id: context.id,
  }),
}

output 从来不是给自己用的

const actor = createActor(machine)
  • 这个 actor 自己拿不到 output
  • output 只会出现在 done 事件里
  • done 事件 是发给父 actor 的

👉 所以本质是:

output = actor 对“外部世界”的返回值