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' })//发送事件,推动状态流转| 对比 | Machine | Actor |
|---|---|---|
| 是否运行 | ❌ 不运行 | ✅ 运行 |
| 是否可复用 | ✅ 可复用 | ❌ 一次实例 |
| 职责 | 描述规则 | 执行规则 |
| 类比 | 类定义 | 类实例 |
提供 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 对“外部世界”的返回值
