从 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',
},
},
});
使用 params
传递参数给动作和守卫
动作对象和守卫对象中除了 type
之外的属性应该嵌套在 params
属性下;{ type: 'someType', message: 'hello' }
变为 { type: 'someType', params: { message: 'hello' }}
。这些 params
然后会作为动作或守卫实现的第二个参数传递:
- XState v5
- XState v4
// ✅
const machine = createMachine({
entry: {
type: 'greet',
params: {
message: 'Hello world',
},
},
on: {
someEvent: {
guard: { type: 'isGreaterThan', params: { value: 42 } },
},
},
}).provide({
actions: {
greet: ({ context, event }, params) => {
console.log(params.message); // 'Hello world'
},
},
guards: {
isGreaterThan: ({ context, event }, params) => {
return event.value > params.value;
},
},
});
// ❌ 已弃用
const machine = createMachine(
{
entry: {
type: 'greet',
message: 'Hello world',
},
on: {
someEvent: {
cond: { type: 'isGreaterThan', value: 42 },
},
},
},
{
actions: {
greet: (context, event, { action }) => {
console.log(action.message); // 'Hello world'
},
},
guards: {
isGreaterThan: (context, event, { guard }) => {
return event.value > guard.value;
},
},
},
);
迁移前提示: 更新 v4 项目中的动作和守卫对象,将 type
之外的属性移动到 params
对象中。
使用通配符 *
转换,而不是严格模式
严格模式已被移除。如果您希望在未处理的事件上抛出错误,您应该使用通配符转换:
- XState v5
- XState v4
// ✅
const machine = createMachine({
on: {
knownEvent: {
// ...
},
'*': {
// unknown event
actions: ({ event }) => {
throw new Error(`Unknown event: ${event.type}`);
},
},
},
});
const actor = createActor(machine);
actor.subscribe({
error: (err) => {
console.error(err);
}
});
actor.start();
actor.send({ type: 'unknownEvent' });
// ❌ 已弃用
const machine = createMachine({
strict: true,
on: {
knownEvent: {
// ...
},
},
});
const service = interpret(machine);
service.send({ type: 'unknownEvent' });
使用显式的无事件(always
)转换
无事件(“always”)转换现在必须通过状态节点的 always: { ... }
属性定义;它们不能再通过空字符串定义:
- XState v5
- XState v4
// ✅
const machine = createMachine({
// ...
states: {
someState: {
always: {
target: 'anotherState',
},
},
},
});
// ❌ 已弃用
const machine = createMachine({
// ...
states: {
someState: {
on: {
'': {
target: 'anotherState',
},
},
},
},
});
迁移前提示: 更新 v4 项目以使用 always
进行无事件转换。
使用 reenter: true
,而不是 internal: false
internal: false
现在是 reenter: true
以前用 internal: false
指定的外部转换现在用 reenter: true
指定:
- XState v5
- XState v4
// ✅
const machine = createMachine({
// ...
on: {
someEvent: {
target: 'sameState',
reenter: true,
},
},
});
// ❌ 已弃用
const machine = createMachine({
// ...
on: {
someEvent: {
target: 'sameState',
internal: false,
},
},
});
转换默认是内部的,而不是外部的
所有转换默认是内部的。此更改与在状态节点上定义的转换相关,这些状态节点具有 entry
或 exit
动作、调用的演员或延迟转换 (after
)。如果您依赖于以前 XState v4 的行为,即转换隐式重新进入状态节点,请使用 reenter: true
:
- XState v5
- XState v4
// ✅
const machine = createMachine({
// ...
states: {
compoundState: {
entry: 'someAction',
on: {
someEvent: {
target: 'compoundState.childState',
// Reenters the `compoundState` state,
// just like an external transition
reenter: true,
},
selfEvent: {
target: 'childState',
reenter: true
}
},
initial: 'childState',
states: {
childState: {},
},
},
},
});
// ❌ 已弃用
const machine = createMachine({
// ...
states: {
compoundState: {
entry: 'someAction',
on: {
someEvent: {
// implicitly external
target: 'compoundState.childState', // non-relative target
},
selfEvent: {
target: 'compoundState'
}
},
initial: 'childState',
states: {
childState: {},
},
},
},
});
- XState v5
- XState v4
// ✅
const machine = createMachine({
// ...
states: {
compoundState: {
after: {
1000: {
target: 'compoundState.childState',
reenter: true, // make it external explicitly!
},
},
initial: 'childState',
states: {
childState: {},
},
},
},
});
// ❌ 已弃用
const machine = createMachine({
// ...
states: {
compoundState: {
after: {
1000: {
// implicitly external
target: 'compoundState.childState', // non-relative target
},
},
initial: 'childState',
states: {
childState: {},
},
},
},
});
子状态节点总是重新进入
当复合状态节点上定义的转换(无论是外部还是内部)目标是子状态节点时,子状态节点总是重新进入。这一变化仅在子状态节点具有 entry
或 exit
动作、调 用的演员或延迟转换 (after
) 时相关。添加 stateIn
守卫以防止子状态的非预期重新进入:
- XState v5
- XState v4
// ✅
const machine = createMachine({
// ...
states: {
compoundState: {
on: {
someEvent: {
guard: not(stateIn({ compoundState: 'childState' })),
target: '.childState',
},
},
initial: 'childState',
states: {
childState: {
entry: 'someAction',
},
},
},
},
})
// ❌ 已弃用
const machine = createMachine({
// ...
states: {
compoundState: {
on: {
someEvent: {
// Implicitly internal; childState not re-entered
target: '.childState',
},
},
initial: 'childState',
states: {
childState: {
entry: 'someAction',
},
},
},
},
});
使用 stateIn()
验证状态转换,而不是 in
in: 'someState'
转换属性已被移除。请改用 guard: stateIn(...)
:
- XState v5
- XState v4
// ✅
const machine = createMachine({
on: {
someEvent: {
guard: stateIn({ form: 'submitting' }),
target: 'someState',
},
},
});
// ❌ 已弃用
const machine = createMachine({
on: {
someEvent: {
in: '#someMachine.form.submitting'
target: 'someState',
},
},
});
使用 actor.subscribe()
代替 state.history
state.history
属性已被移除。如果您需要前一个快照,应该通过 actor.subscribe(...)
来维护。
- XState v5
- XState v4
// ✅
let previousSnapshot = actor.getSnapshot();
actor.subscribe((snapshot) => {
doSomeComparison(previousSnapshot, snapshot);
previousSnapshot = snapshot;
});
// ❌ 已弃用
actor.subscribe((state) => {
doSomeComparison(state.history, state);
});
迁移前提示: 更新 v4 项目以使用 actor.subscribe()
跟踪历史记录。
动作可以抛出错误而无需 escalate
escalate
动作创建器已被移除。在 XState v5 中,动作可以抛出错误,并且它们将按预期传播。错误可以使用 onError
转换进行处理。
- XState v5
- XState v4
// ✅
const childMachine = createMachine({
// 这将发送到调用此子机器的父机器
entry: () => {
throw new Error('This is some error')
}
});
const parentMachine = createMachine({
invoke: {
src: childMachine,
onError: {
actions: ({ context, event }) => {
console.log(event.error);
// {
// type: ...,
// error: {
// message: 'This is some error'
// }
// }
}
}
}
});
// ❌ 已弃用
const childMachine = createMachine({
entry: escalate('This is some error')
});
/* ... */
Actors
使用 actor 逻辑创建器代替函数作为 invoke.src
可用的 actor 逻辑创建器有:
createMachine
fromPromise
fromObservable
fromEventObservable
fromTransition
fromCallback
有关更多信息,请参阅 Actors。
- XState v5
- XState v4
// ✅
import { fromPromise, setup } from 'xstate';
const machine = setup({
actors: {
getUser: fromPromise(async ({ input }: { input: { userId: string } }) => {
const data = await getData(input.userId);
// ...
return data;
})
}
}).createMachine({
invoke: {
src: 'getUser',
input: ({ context, event }) => ({
userId: context.userId,
}),
},
});
// ❌ 已弃用
import { createMachine } from 'xstate';
const machine = createMachine({
invoke: {
src: (context) => async () => {
const data = await getData(context.userId);
// ...
return data;
},
},
});
- XState v5
- XState v4
// ✅
import { fromCallback, createMachine } from 'xstate';
const machine = createMachine({
invoke: {
src: fromCallback(({ sendBack, receive, input }) => {
// ...
}),
input: ({ context, event }) => ({
userId: context.userId,
}),
},
});
// ❌ 已弃用
import { createMachine } from 'xstate';
const machine = createMachine({
invoke: {
src: (context, event) => (sendBack, receive) => {
// context.userId
// ...
},
},
});
- XState v5
- XState v4
// ✅
import { fromEventObservable, createMachine } from 'xstate';
import { interval, mapTo } from 'rxjs';
const machine = createMachine({
invoke: {
src: fromEventObservable(() =>
interval(1000).pipe(mapTo({ type: 'tick' })),
),
},
});
// ❌ 已弃用
import { createMachine } from 'xstate';
import { interval, mapTo } from 'rxjs';
const machine = createMachine({
invoke: {
src: () => interval(1000).pipe(mapTo({ type: 'tick' })),
},
});
使用 invoke.input
代替 invoke.data
invoke.data
属性已被移除。如果您想为被调用的演员提供上下文,请使用 invoke.input
:
- XState v5
- XState v4
// ✅
const someActor = createMachine({
// The input must be consumed by the invoked actor:
context: ({ input }) => input,
// ...
});
const machine = createMachine({
// ...
invoke: {
src: 'someActor',
input: {
value: 42,
},
},
});
// ❌ 已弃用
const someActor = createMachine({
// ...
});
const machine = createMachine({
// ...
invoke: {
src: 'someActor',
data: {
value: 42,
},
},
});
在最终状态中使用 output
代替 data
要从达到最终状态的机器生成输出数据,请使用顶级 output
属性代替 data
:
- XState v5
- XState v4
// ✅
const machine = createMachine({
// ...
states: {
finished: {
type: 'final',
},
},
output: {
answer: 42,
},
});
// ❌ 已弃用
const machine = createMachine({
// ...
states: {
finished: {
type: 'final',
data: {
answer: 42,
},
},
},
});
要提供动态生成的输出,请将 invoke.data
替换为 invoke.output
并添加顶级 output
属性:
- XState v5
- XState v4
// ✅
const machine = createMachine({
// ...
states: {
finished: {
type: 'final',
output: ({ event }) => ({
answer: event.someValue,
}),
},
},
output: ({ event }) => event.output,
});
// ❌ 已弃用
const machine = createMachine({
// ...
states: {
finished: {
type: 'final',
data: (context, event) => {
answer: event.someValue,
},
},
},
});
不要在 input
或 output
中使用属性映射器
如果您想为被调用的演员提供动态上下文,或从最终状态生成动态输出,请使用函数而不是带有属性映射器的对象。
- XState v5
- XState v4
// ✅
const machine = createMachine({
// ...
invoke: {
src: 'someActor',
input: ({ context, event }) => ({
value: event.value,
}),
},
});
// 被调用的 actor 必须消费 input:
const someActor = createMachine({
// ...
context: ({ input }) => input,
});
// 生成机器输出
const machine = createMachine({
// ...
states: {
finished: {
type: 'final',
},
},
output: ({ context, event }) => ({
answer: context.value,
}),
});
// ❌ 已弃用
const machine = createMachine({
// ...
invoke: {
src: 'someActor',
data: {
value: (context, event) => event.value, // a property mapper
},
},
});
// 生成机器输出
const machine = createMachine({
// ...
states: {
finished: {
type: 'final',
data: {
answer: (context, event) => context.value, // a property mapper
},
},
},
});
使用 options
对象上的 actors
属性代替 services
services
已重命名为 actors
:
- XState v5
- XState v4
// ✅
const specificMachine = machine.provide({
actions: {
/* ... */
},
guards: {
/* ... */
},
actors: {
/* ... */
},
// ...
});
// ❌ 已弃用
const specificMachine = machine.withConfig({
actions: {
/* ... */
},
guards: {
/* ... */
},
services: {
/* ... */
},
// ...
});
使用 subscribe()
监听变化,而不是 onTransition()
actor.onTransition(...)
方法已被移除。请改用 actor.subscribe(...)
。
- XState v5
- XState v4
// ✅
const actor = createActor(machine);
actor.subscribe((state) => {
// ...
});
// ❌ 已弃用
const actor = interpret(machine);
actor.onTransition((state) => {
// ...
});
createActor()
(原 interpret()
)接受第二个参数来恢复状态
interpret(machine).start(state)
现在是 createActor(machine, { snapshot }).start()
要在特定状态下恢复演员,现在应将状态作为 createActor(logic, options)
的 options
参数的 snapshot
属性传递。actor.start()
方法不再接受 state
参数。
- XState v5
- XState v4
// ✅
const actor = createActor(machine, { snapshot: someState });
actor.start();
// ❌ 已弃用
const actor = interpret(machine);
actor.start(someState);
使用 actor.getSnapshot()
获取演员的状态
在演员启动后订阅演员 (actor.subscribe(...)
) 将不再立即发出当前快照。相反,请从 actor.getSnapshot()
读取当前快照:
- XState v5
- XState v4
// ✅
const actor = createActor(machine);
actor.start();
const initialState = actor.getSnapshot();
actor.subscribe((state) => {
// Snapshots from when the subscription was created
// Will not emit the current snapshot until a transition happens
});
// ❌ 已弃用
const actor = interpret(machine);
actor.start();
actor.subscribe((state) => {
// Current snapshot immediately emitted
});
遍历事件而不是使用 actor.batch()
用于批量处理事件的 actor.batch([...])
方法已被移除。
- XState v5
- XState v4
// ✅
for (const event of events) {
actor.send(event);
}
// ❌ 已弃用
actor.batch(events);
迁移前提示: 更新 v4 项目以循环遍历事件并将它们作为批处理发送。
使用 snapshot.status === 'done'
代替 snapshot.done
snapshot.done
属性已被移除,该属性以前在状态机演员的快照对象中。请改用 snapshot.status === 'done'
,它适用于所有演员:
- XState v5
- XState v4
// ✅
const actor = createActor(machine);
actor.start();
actor.subscribe((snapshot) => {
if (snapshot.status === 'done') {
// ...
}
});
// ❌ 已弃用
const actor = interpret(machine);
actor.start();
actor.subscribe((state) => {
if (state.done) {
// ...
}
});
state.nextEvents
已被移除
state.nextEvents
属性已被移除,因为它不是确定可以发送给演员的下一个事件的完全安全/可靠的方法。如果您想根据以前的行为获取下一个事件,可以使用此辅助函数:
import type { AnyMachineSnapshot } from 'xstate';
function getNextEvents(snapshot: AnyMachineSnapshot) {
return [...new Set([...snapshot._nodes.flatMap((sn) => sn.ownEvents)])];
}
// 代替 `state.nextEvents`:
const nextEvents = getNextEvents(state);
TypeScript
使用 types
代替 schema
machineConfig.schema
属性已重命名 为 machineConfig.types
:
- XState v5
- XState v4
// ✅
const machine = createMachine({
types: {} as {
context: {
/* ...*/
};
events: {
/* ...*/
};
},
});
// ❌ 已弃用
const machine = createMachine({
schema: {} as {
context: {
/* ...*/
};
events: {
/* ...*/
};
},
});
使用 types.typegen
代替 tsTypes
machineConfig.tsTypes
属性已被重命名,现在位于 machineConfig.types.typegen
。
- XState v5
- XState v4
// ✅
const machine = createMachine({
types: {} as {
typegen: {};
context: {
/* ...*/
};
events: {
/* ...*/
};
},
});
// ❌ 已弃用
const machine = createMachine({
tsTypes: {};
schema: {} as {
context: {
/* ...*/
};
events: {
/* ...*/
};
},
});
@xstate/react
useInterpret()
现在是 useActorRef()
useInterpret()
钩子(用于返回 actorRef
,在 XState v4 中称为 "service")已重命名为 useActorRef()
。
- XState v5
- XState v4
// ✅
import { useActorRef } from '@xstate/react';
const actorRef = useActorRef(machine); // or any other logic
// ❌ 已弃用
import { useInterpret } from '@xstate/react';
const service = useInterpret(machine);
useActor(logic)
现在接受 actor 逻辑,而不是 actor
useActor(logic)
钩子现在接受 actor 逻辑(例如 fromPromise(...)
、createMachine(...)
等)而不是现有的 ActorRef
。
要使用现有的 ActorRef
,请使用 actor.send(...)
发送事件,并使用 useSelector(actor, ...)
获取快照:
- XState v5
- XState v4
// ✅
import { useSelector } from '@xstate/react';
function Component({ someActorRef }) {
const state = useSelector(someActorRef, (s) => s);
return (
<button onClick={() => someActorRef.send({ type: 'someEvent' })} />
);
}
// ❌ 已弃用
import { useActor } from '@xstate/react';
function Component({ someActorRef }) {
const [state, send] = useActor(someActorRef);
return (
<button onClick={() => send({ type: 'someEvent' })} />
);
}
使用 machine.provide()
在钩子中提供实现
对于动态创建具有提供实现的机器,useMachine(...)
、useActor(...)
和 useActorRef(...)
钩子不再接受:
- 作为第一个参数的惰性机器创建器
- 传递给第二个参数的实现
相反,machine.provide(...)
应直接传递给第一个参数。
@xstate/react
包将具有相同配置的机器视为相同的机器,因此它将最小化重新渲染,但仍会保持提供的实现是最新的。
- XState v5
- XState v4
- XState v4
// ✅
import { useMachine } from '@xstate/react';
import { someMachine } from './someMachine';
function Component(props) {
const [state, send] = useMachine(someMachine.provide({
actions: {
doSomething: () => {
props.onSomething?.(); // Kept up-to-date
}
}
}));
// ...
}
// ❌ 已弃用
import { useMachine } from '@xstate/react';
import { someMachine } from './someMachine';
function Component(props) {
const [state, send] = useMachine(someMachine, {
actions: {
doSomething: () => {
props.onSomething?.();
}
}
});
// ...
}
// ❌ 已弃用
import { useMachine } from '@xstate/react';
import { someMachine } from './someMachine';
function Component(props) {
const [state, send] = useMachine(() => someMachine.withConfig({
actions: {
doSomething: () => {
props.onSomething?.();
}
}
}));
// ...
}
@xstate/vue
useMachine()
现在返回 snapshot
而不是 state
,并返回 actor
而不是 service
为了与 XState 和相关库保持一致的命名:
state
现在是snapshot
service
现在是actor
- XState v5
- XState v4
// ✅
import { useMachine } from '@xstate/vue';
// ...
const {
snapshot, // Renamed from `state`
send,
actor // Renamed from `service`
} = useMachine(someMachine)
// ❌ 已弃用
import { useMachine } from '@xstate/vue';
// ...
const {
state, // Renamed to `snapshot` in @xstate/vue 3.0.0
send,
service // Renamed to `actor` in @xstate/vue 3.0.0
} = useMachine(someMachine)
新功能
- 创建演员系统
- 新的演员逻辑创建器
- 为调用和生成的演员提供深度持久性
- 为状态机和演员提供输入数据
- 为演员指定输出“完成数据”
- 部分事件描述符(部分通配符)
- 排队动作
- 更高级的守卫
- 用于指定类型和强类型状态值的设置 API
- 检查 API
常见问题
Stately Studio 何时会兼容 XState v5?
我们目前正在努力使 Stately Studio 兼容 XState v5。导出到 XState v5(JavaScript 或 TypeScript)已经可用。我们正在努力支持 XState v5 的新功能,例如高阶守卫、部分事件通配符和机器输入/输出。
在我们的路线图上为 Stately Studio + XState v5 兼容性 点赞或发表评论,以随时了解我们的进展。
XState VS Code 扩展何时会兼容 XState v5?
XState VS Code 扩展 尚未兼容 XState v5。该扩展是我们的优先事项,相关工作已经在进行中。
在我们的路线图上为 VS Code 扩展的 XState v5 兼容性 点赞或发表评论,以随时了解我们的进展。
XState v5 何时会有 typegen?
XState v5 中的 TypeScript 推断功能得到了极大改进。特别是通过 setup()
API 和动态参数等功能,主要的 typegen 用例不再需要。
然而,我们认识到可能仍然存在一些特定的 typegen 用例。在我们的路线图上为 XState v5 的 typegen 点赞或发表评论,以随时了解我们的进展。
如何同时使用 XState v4 和 v5?
您可以在同一个项目中同时使用 XState v4 和 v5,这对于逐步迁移到 XState v5 非常有用。要同时使用这两个版本,请手动或通过 CLI 将 "xstate5": "npm:xstate@5"
添加到您的 package.json
中:
npm i xstate5@npm:xstate@5
然后,您可以在代码中导入 XState v5 版本:
import { createMachine } from 'xstate5';
// or { createMachine as createMachine5 } from 'xstate5';
如果您需要使用不同版本的集成包,例如 @xstate/react
,您可以使用类似上面的策略,但需要在集成包中链接到正确版本的 XState。这可以通过使用脚本来完成:
npm i xstate5@npm:xstate@5 @xstate5/react@npm:@xstate/react@4 --force
// scripts/xstate5-react-script.js
const fs = require('fs-extra');
const path = require('path');
const rootNodeModules = path.join(__dirname, '..', 'node_modules');
fs.ensureSymlinkSync(
path.join(rootNodeModules, 'xstate5'),
path.join(rootNodeModules, '@xstate5', 'react', 'node_modules', 'xstate'),
);
// package.json
"scripts": {
"postinstall": "node scripts/xstate5-react-script.js"
}
然后,您可以在代码中使用兼容 XState v5 的 @xstate/react
版本:
import { useMachine } from '@xstate5/react';
// or { useMachine as useMachine5 } from '@xstate5/react';
import { createMachine } from 'xstate5';
// or { createMachine as createMachine5 } from 'xstate5';
// ...