从 XState v4 迁移到 v5
下面的指南解释了如何从 XState 版本 4 迁移到版本 5。从 XState v4 迁移到 v5 应该是一个简单的过程。如果您遇到困难或有任何问题,请通过 我们的 Discord 联系 Stately 团队。
本指南适用于希望将代码库从 v4 更新到 v5 的开发人员,也适用于任何希望了解 v4 和 v5 之间差异的开发人员。
XState v5 和 TypeScript
XState v5 及其相关库是用 TypeScript 编写的,并利用复杂的类型为您提供最佳的类型安全性和推断能力。XState v5 需要 TypeScript 版本 5.0 或更高版本。 为了获得最佳效果,请使用最新的 TypeScript 版本。
请遵循以下指南以确保您的 TypeScript 项目准备好使用 XState v5:
-
使用最新版本的 TypeScript,版本 5.0 或更高(必需)
npm install typescript@latest --save-dev
-
在您的
tsconfig.json
文件中将strictNullChecks
设置为true
。这将确保我们的类型正常工作,并有助于捕捉代码中的错误(强烈推荐)// tsconfig.json
{
"compilerOptions": {
// ...
"strictNullChecks": true
// 或将 `strict` 设置为 true,这包括 `strictNullChecks`
// "strict": true
}
} -
在您的
tsconfig.json
文件中将skipLibCheck
设置为true
(推荐)
创建机器和演员
使用 createMachine()
,而不是 Machine()
Machine(config)
函数现在称为 createMachine(config)
:
- XState v5
- XState v4
import { createMachine } from 'xstate';
const machine = createMachine({
// ...
});
// ❌ 已弃用
import { Machine } from 'xstate';
const machine = Machine({
// ...
});
使用 createActor()
,而不是 interpret()
interpret()
函数已重命名为 createActor()
:
- XState v5
- XState v4
import { createMachine, createActor } from 'xstate';
const machine = createMachine(/* ... */);
// ✅
const actor = createActor(machine, {
// actor 选项
});
import { createMachine, interpret } from 'xstate';
const machine = createMachine(/* ... */);
// ❌ 已弃用
const actor = interpret(machine, {
// actor 选项
});
使用 machine.provide()
,而不是 machine.withConfig()
machine.withConfig()
方法已重命名为 machine.provide()
:
- XState v5
- XState v4
// ✅
const specificMachine = machine.provide({
actions: {
/* ... */
},
guards: {
/* ... */
},
actors: {
/* ... */
},
// ...
});
// ❌ 已弃用
const specificMachine = machine.withConfig({
actions: {
/* ... */
},
guards: {
/* ... */
},
services: {
/* ... */
},
// ...
});
使用 input
设置上下文,而不是 machine.withContext()
machine.withContext(...)
方法不能再使用,因为 context
不能再直接覆盖。请改用 input:
- XState v5
- XState v4
// ✅
const machine = createMachine({
context: ({ input }) => ({
actualMoney: Math.min(input.money, 42),
}),
});
const actor = createActor(machine, {
input: {
money: 1000,
},
});
// ❌ 已弃用
const machine = createMachine({
context: {
actualMoney: 0,
},
});
const moneyMachine = machine.withContext({
actualMoney: 1000,
});
动作默认按顺序执行,不再需要 predictableActionArguments
动作现在默认按顺序执行,因此不再需要 predictableActionArguments
标志。分配动作将始终按定义的顺序运行。
- XState v5
- XState v4
// ✅
const machine = createMachine({
entry: [
({ context }) => {
console.log(context.count); // 0
},
assign({ count: 1 }),
({ context }) => {
console.log(context.count); // 1
},
assign({ count: 2 }),
({ context }) => {
console.log(context.count); // 2
},
],
});
// ❌ 已弃用
const machine = createMachine({
predictableActionArguments: true,
entry: [
(context) => {
console.log(context.count); // 0
},
assign({ count: 1 }),
(context) => {
console.log(context.count); // 1
},
assign({ count: 2 }),
(context) => {
console.log(context.count); // 2
},
],
});
spawn()
函数已被移除
不再使用导入的 spawn()
函数在 assign(...)
动作中 创建演员:
- 使用
spawnChild(...)
动作创建器(首选) - 或者在
assign(...)
动作中使用传递给分配函数的第一个参数中的spawn(...)
方法(如果需要在context
中使用演员引用)
阅读有关生成演员的文档以获取更多信息。
- XState v5
- XState v4
// ✅
import { spawnChild, assign } from 'xstate';
// 生成一个直接子演员:
const machine1 = createMachine({
// ...
entry: spawnChild('someChildLogic', {
id: 'someChild',
})
});
// 生成一个在 [`context`](command:_github.copilot.openSymbolFromReferences?%5B%22%22%2C%5B%7B%22uri%22%3A%7B%22scheme%22%3A%22file%22%2C%22authority%22%3A%22%22%2C%22path%22%3A%22%2FD%3A%2FDOC%2Fxstate-doc%2Fdocs%2Fmigration.mdx%22%2C%22query%22%3A%22%22%2C%22fragment%22%3A%22%22%7D%2C%22pos%22%3A%7B%22line%22%3A216%2C%22character%22%3A2%7D%7D%5D%2C%221e3bedd9-7dd0-41fd-9f83-d76e3d7b4562%22%5D "Go to definition") 中有演员引用的子演员:
const machine2 = createMachine({
// ...
entry: assign({
child: ({ spawn }) => spawn('someChildLogic')
})
});
// ❌
import { assign, spawn } from 'xstate';
const machine = createMachine({
// ...
entry: assign({
child: () => spawn('someChildLogic')
})
});
使用 getNextSnapshot(…)
代替 machine.transition(…)
machine.transition(…)
方法现在需要一个“演员范围”作为第三个参数,该参数由 createActor(…)
内部创建。相反,使用 getNextSnapshot(…)
根据当前快照和事件从某些演员逻辑中获取下一个快照:
- XState v5
- XState v4
// ✅
import {
createMachine,
getNextSnapshot
} from 'xstate';
const machine = createMachine({
// ...
});
const nextState = getNextSnapshot(
machine,
machine.resolveState({ value: 'green' }),
{ type: 'timer' }
);
nextState.value; // yellow
// ❌
import { createMachine } from 'xstate';
const machine = createMachine({
// ...
});
const nextState = machine.transition(
'green',
{ type: 'timer' }
);
nextState.value; // yellow
显式发送事件而不是使用 autoForward
autoForward
属性在调用配置中已被移除。相反,请显式发送事件。
通常,不推荐将所有事件转发给演员。相反,只转发演员需要 的特定事件。
- XState v5
- XState v4
// ✅
const machine = createMachine({
// ...
invoke: {
src: 'someSource',
id: 'someId'
},
always: {
// 将事件转发给被调用的演员
// 这在 XState v5 中不会导致无限循环
actions: sendTo('someId', ({ event }) => event)
}
});
// ❌
const machine = createMachine({
// ...
invoke: {
src: 'someSource',
id: 'someId'
autoForward: true // deprecated
}
});
状态
使用 state.getMeta()
代替 state.meta
state.meta
属性已重命名为 state.getMeta()
:
- XState v5
- XState v4
// ✅
state.getMeta();
// ❌ 已弃用
state.meta;
state.toStrings()
方法已被移除
import { type StateValue } from 'xstate';
export function getStateValueStrings(stateValue: StateValue): string[] {
if (typeof stateValue === 'string') {
return [stateValue];
}
const valueKeys = Object.keys(stateValue);
return valueKeys.concat(
...valueKeys.map((key) =>
getStateValueStrings(stateValue[key]!).map((s) => key + '.' + s)
)
);
}
// ...
const stateValueStrings = getStateValueStrings(stateValue);
// 例如 ['green', 'yellow', 'red', 'red.walk', 'red.wait', …]
使用 state._nodes
代替 state.configuration
state.configuration
属性已重命名为 state._nodes
:
- XState v5
- XState v4
// ✅
state._nodes;
// ❌ 已弃用
state.configuration;
从检查 API 读取事件,而不是 state.events
state.events
属性已被移除,因为事件不是状态的一部分,除非您明确将它们添加到状态的 context
中。请使用 检查 API 来观察事件,或者将事件明确添加到状态的 context
中:
- XState v5
- XState v5 (context)
- XState v4
// ✅
import { createActor } from 'xstate';
import { someMachine } from './someMachine';
const actor = createActor(someMachine, {
inspect: (inspEvent) => {
if (inspEvent.type === '@xstate.event') {
console.log(inspEvent.event);
}
}
});
// ✅
import { setup, createActor } from 'xstate';
const someMachine = setup({
// ...
actions: {
recordEvent: assign({
event: ({ event }) => event
})
}
}).createMachine({
context: { event: undefined },
on: {
someEvent: {
// ...
actions: ['recordEvent']
}
}
});
const someActor = createActor(someMachine);
someActor.subscribe(snapshot => {
console.log(snapshot.context.event);
});
// ❌ 已弃用
import { interpret } from 'xstate';
import { someMachine } from './someMachine';
const actor = interpret(someMachine);
actor.subscribe(state => {
console.log(state.event); // Removed
});
事件和转换
实现函数接收 单个参数
实现函数现在接收一个单一参数:一个包含 context
、event
和其他属性的对象。
- XState v5
- XState v4
// ✅
const machine = createMachine({
entry: ({ context, event }) => {
// ...
},
});
// ❌ 已弃用
const machine = createMachine({
entry: (context, event) => {
// ...
},
});
send()
已被移除;请使用 raise()
或 sendTo()
send(...)
动作创建器已被移除。请使用 raise(...)
发送事件给自己,或使用 sendTo(...)
发送事件给其他演员。
阅读有关 sendTo
动作 和 raise
动作 的文档以获取更多信息。
- XState v5
- XState v4
// ✅
const machine = createMachine({
// ...
entry: [
// Send an event to self
raise({ type: 'someEvent' }),
// Send an event to another actor
sendTo('someActor', { type: 'someEvent' }),
],
});
// ❌ 已弃用
const machine = createMachine({
// ...
entry: [
// Send an event to self
send({ type: 'someEvent' }),
// Send an event to another actor
send({ type: 'someEvent' }, { to: 'someActor' }),
],
});
迁移前提示: 更新 v4 项目以使用 sendTo
或 raise
代替 send
。
使用 enqueueActions()
代替 pure()
和 choose()
pure()
和 choose()
方法已被移除。请改用 enqueueActions()
。
对于 pure()
动作:
- XState v5
- XState v4
// ✅
entry: [
enqueueActions(({ context, event, enqueue }) => {
enqueue('action1');
enqueue('action2');
})
];
// ❌ 已弃用
entry: [
pure(() => {
return [
'action1',
'action2'
]
})
];
对于 choose()
动作:
- XState v5
- XState v4
// ✅
entry: [
enqueueActions(({ enqueue, check }) => {
if (check('someGuard')) {
enqueue('action1');
enqueue('action2');
}
})
];
// ❌ 已弃用
entry: [
choose([
{
guard: 'someGuard',
actions: ['action1', 'action2']
}
]),
];
actor.send()
不再接受字符串类型
字符串事件类型不再可以发送,例如 actor.send(event)
;您必须发送一个事件对象:
- XState v5
- XState v4
// ✅
actor.send({ type: 'someEvent' });
// ❌ 已弃用
actor.send('someEvent');
迁移前提示: 更新 v4 项目以传递对象给 .send()
。
state.can()
不再接受字符串类型
字符串事件类型不再可以发送,例如 state.can(event)
;您必须发送一个事件对象:
- XState v5
- XState v4
// ✅
state.can({ type: 'someEvent' });
// ❌ 已弃用
state.can('someEvent');
受保护的转换使用 guard
,而不是 cond
受保护转换的 cond
属性现在称为 guard
:
- XState v5
- XState v4
// ✅
const machine = createMachine({
on: {
someEvent: {
guard: 'someGuard',
target: 'someState',
},
},
});
// ❌ 已弃用
const machine = createMachine({
on: {
someEvent: {
// renamed to `guard` in v5
cond: 'someGuard',
target: 'someState',
},
},
});