Actions(动作)
动作是一次性执行的效果。当状态机转换时,它可能会执行动作。动作响应事件发生,通常在转换中的 actions: [...] 属性中定义。动作也可以在进入状态的任何转换中定义在状态的 entry: [...] 属性中,或者在退出状态的任何转换中定义在状态的 exit: [...] 属性中。
动作也可以在状态的 entry 或 exit 上定义,可以是单个动作或数组。
import { setup } from 'xstate';
function trackResponse(response: string) {
// ...
}
const feedbackMachine = setup({
actions: {
track: (_, params: { response: string }) => {
trackResponse(params.response);
// 记录 { response: 'good' }
},
showConfetti: () => {
// ...
}
}
}).createMachine({
// ...
states: {
// ...
question: {
on: {
'feedback.good': {
actions: [
{ type: 'track', params: { response: 'good' } }
]
}
},
exit: [
{ type: 'exitAction' }
]
}
thanks: {
entry: [
{ type: 'showConfetti' }
],
}
}
});
动作示例:
- 记录消息
- 发送消息到另一个actor
- 更新上下文
进入和退出动作
进入动作是在任何进入状态节点的转换上发生的动作。退出动作是在任何退出状态节点的转换上发生的动作。
进入和退出动作使用状态节点上的 entry: [...] 和 exit: [...] 属性定义。您可以在一个状态上触发多个进入和退出动作。顶级最终状态不能有退出动作,因为机器已停止且无法再进行转换。
动作对象
动作对象有一个动作 type 和一个可选的 params 对象:
- 动作的
type属性描述动作。具有相同类型的动作具有相同的实现。 - 动作的
params属性包含与动作相关的参数化值。
import { setup } from 'xstate';
const feedbackMachine = setup({
actions: {
track: (_, params: { response: string }) => {
/* ... */
},
},
}).createMachine({
// ...
states: {
// ...
question: {
on: {
'feedback.good': {
actions: [
{
// 动作类型
type: 'track',
// 动作参数
params: { response: 'good' },
},
],
},
},
},
},
});
动态动作参数
您可以通过使用返回参数的函数在 params 属性中动态传递参数。该函数接受一个包含当前 context 和 event 的对象作为参数。
import { setup } from 'xstate';
const feedbackMachine = setup({
actions: {
logInitialRating: (_, params: { initialRating: number }) => {
// ...
},
},
}).createMachine({
context: {
initialRating: 3,
},
entry: [
{
type: 'logInitialRating',
params: ({ context }) => ({
initialRating: context.initialRating,
}),
},
],
});
这是使动作更具可重用性的推荐方法,因为您可以定义不依赖于机器的 context 或 event 类型的动作。
import { setup } from 'xstate';
function logInitialRating(_, params: { initialRating: number }) {
console.log(`Initial rating: ${params.initialRating}`);
}
const feedbackMachine = setup({
actions: { logInitialRating },
}).createMachine({
context: { initialRating: 3 },
entry: [
{
type: 'logInitialRating',
params: ({ context }) => ({
initialRating: context.initialRating,
}),
},
],
});
内联动作
您可以将动作声明为内联函数:
import { createMachine } from 'xstate';
const feedbackMachine = createMachine({
entry: [
// 内联动作
({ context, event }) => {
console.log(/* ... */);
},
],
});
内联动作对于原型设计和简单情况非常有用,但我们通常建议使用动作对象。
实现动作
您可以在 setup(...) 函数的 actions 属性中设置命名动作的实现
import { setup } from 'xstate';
const feedbackMachine = setup({
actions: {
track: (_, params: { msg: string }) => {
// 动作实现
// ...
},
},
}).createMachine({
// 机器配置
entry: [{ type: 'track', params: { msg: 'entered' } }],
});
您还可以在 machine.provide(...) 方法中提供动作实现,以覆盖现有的动作实现。该方法会创建一个具有相同配置但使用提供的实现的新机器:
const feedbackActor = createActor(
feedbackMachine.provide({
actions: {
track: ({ context, event }, params) => {
// 不同的动作实现
// (覆盖之前的实现)
// ...
},
},
}),
);
内置动作
XState 提供了许多有用的内置动作,它们是您的状态机逻辑的核心部分,而不仅仅是副作用。
Assign 动作
assign(...) 动作是一种特殊的动作,用于将数据分配到状态上下文中。在 assign(assignments) 中的 assignments 参数是指定上下文分配的地方。
分配可以是键值对的对象,其中键是 context 键,值可以是静态值或返回新值的表达式:
import { setup } from 'xstate';
const countMachine = setup({
types: {
events: {} as { type: 'increment'; value: number },
},
}).createMachine({
context: {
count: 0,
},
on: {
increment: {
actions: assign({
count: ({ context, event }) => context.count + event.value,
}),
},
},
});
const countActor = createActor(countMachine);
countActor.subscribe((state) => {
console.log(state.context.count);
});
countActor.start();
// logs 0
countActor.send({ type: 'increment', value: 3 });
// logs 3
countActor.send({ type: 'increment', value: 2 });
// logs 5
对于更动态的分配,传递给 assign(...) 的参数也可以是一个返回部分或全部 context 值的函数:
import { setup } from 'xstate';
const countMachine = setup({
types: {
events: {} as { type: 'increment'; value: number },
},
}).createMachine({
context: {
count: 0,
},
on: {
increment: {
actions: assign(({ context, event }) => {
return {
count: context.count + event.value,
};
}),
},
},
});
Raise 动作
Raise 动作是一种特殊的动作,用于“触发”一个事件,该事件由同一个状态机接收。触发事件是状态机向自身“发送”事件的方式:
import { createMachine, raise } from 'xstate';
const machine = createMachine({
// ...
entry: raise({ type: 'someEvent', data: 'someData' });
});
内部,当一个事件被触发时,它会被放入一个“内部事件队列”。在当前转换完成后,这些事件将按插入顺序(先进先出,或 FIFO)进行处理。只有在内部事件队列中的所有事件都被处理完后,外部事件才会被处理。
触发的事件可以是动态的:
import { createMachine, raise } from 'xstate';
const machine = createMachine({
// ...
entry: raise(({ context, event }) => ({
type: 'dynamicEvent',
data: context.someValue,
})),
});
事件也可以在延迟后触发,这样它们不会 被放入内部事件队列,因为它们不会立即被处理:
import { createMachine, raise } from 'xstate';
const machine = createMachine({
// ...
entry: raise(
{ type: 'someEvent' },
{ delay: 1000 }
);
});
Send-to 动作
sendTo(...) 动作是一种特殊的动作,用于向特定的 actor 发送事件。
const machine = createMachine({
on: {
transmit: {
actions: sendTo('someActor', { type: 'someEvent' }),
},
},
});
事件可以是动态的:
const machine = createMachine({
on: {
transmit: {
actions: sendTo('someActor', ({ context, event }) => {
return { type: 'someEvent', data: context.someData };
}),
},
},
});
目标 actor 可以是 actor 的 ID 或 actor 引用本身:
const machine = createMachine({
context: ({ spawn }) => ({
someActorRef: spawn(fromPromise(/* ... */)),
}),
on: {
transmit: {
actions: sendTo(({ context }) => context.someActorRef, {
type: 'someEvent',
}),
},
},
});
其他选项,例如 delay 和 id,可以作为第三个参数传递:
const machine = createMachine({
on: {
transmit: {
actions: sendTo(
'someActor',
{ type: 'someEvent' },
{
id: 'transmission',
delay: 1000,
},
),
},
},
});
延迟动作可以通过它们的 id 取消。请参阅 cancel(...)。
Send-parent 动作
sendParent(...) 动作是一种特殊的动作,用于向父 actor 发送事件(如果存在父 actor)。
排队动作
enqueueActions(...) 动作创建器是一个高级动作,它将动作按顺序排队执行,而不实际执行任何动作。它接受一个回调,该回调接收 context、event 以及 enqueue 和 check 函数:
-
enqueue(...)函数用于排队一个动作。它接受一个动作对象或动作函数:actions: enqueueActions(({ enqueue }) => {
// 排队一个动作对象
enqueue({ type: 'greet', params: { message: 'hi' } });
// 排队一个动作函数
enqueue(() => console.log('Hello'));
// 排队一个没有参数的简单动作
enqueue('doSomething');
}); -
check(...)函数用于有条件地排队一个动作。它接受一个守卫对象或守卫函数,并返回一个表示守卫是否评估为true的布尔值:actions: enqueueActions(({ enqueue, check }) => {
if (check({ type: 'everythingLooksGood' })) {
enqueue('doSomething');
}
}); -
enqueue上还有一些辅助 方法用于排队内置动作:enqueue.assign(...):排队一个assign(...)动作enqueue.sendTo(...):排队一个sendTo(...)动作enqueue.raise(...):排队一个raise(...)动作enqueue.spawnChild(...):排队一个spawnChild(...)动作enqueue.stopChild(...):排队一个stopChild(...)动作enqueue.cancel(...):排队一个cancel(...)动作
排队的动作可以有条件地调用,但不能异步排队。
const machine = createMachine({
// ...
entry: enqueueActions(({ context, event, enqueue, check }) => {
// assign 动作
enqueue.assign({
count: context.count + 1,
});
// 条件动作(替代 choose(...))
if (event.someOption) {
enqueue.sendTo('someActor', { type: 'blah', thing: context.thing });
// 其他动作
enqueue('namedAction');
// 带参数
enqueue({ type: 'greet', params: { message: 'hello' } });
} else {
// 内联
enqueue(() => console.log('hello'));
// 甚至内置动作
}
// 使用 check(...) 基于守卫条件性地排队动作
if (check({ type: 'someGuard' })) {
// ...
}
// 无返回值
}),
});
您可以使用带有引用的排队动作的参数:
import { setup, enqueueActions } from 'xstate';
const machine = setup({
actions: {
doThings: enqueueActions(({ enqueue }, params: { name: string }) => {
enqueue({ type: 'greet', params: { name } });
// ...
}),
greet: (_, params: { name: string }) => {
console.log(`Hello ${params.name}!`);
},
},
}).createMachine({
// ...
entry: {
type: 'doThings',
params: { name: 'World' },
},
});
日志动作
log(...) 动作是一种 简单的方法,可以将消息记录到控制台。
import { createMachine, log } from 'xstate';
const machine = createMachine({
on: {
someEvent: {
actions: log('some message'),
},
},
});
取消动作
cancel(...) 动作通过其 ID 取消延迟的 sendTo(...) 或 raise(...) 动作:
import { createMachine, sendTo, cancel } from 'xstate';
const machine = createMachine({
on: {
event: {
actions: sendTo(
'someActor',
{ type: 'someEvent' },
{
id: 'someId',
delay: 1000,
},
),
},
cancelEvent: {
actions: cancel('someId'),
},
},
});
停止子 actor 动作
stopChild(...) 动作用于停止子 actor。actor 只能从其父 actor 停止:
import { createMachine, stopChild } from 'xstate';
const machine = createMachine({
context: ({ spawn }) => ({
spawnedRef: spawn(fromPromise(/* ... */), { id: 'spawnedId' }),
}),
on: {
stopById: {
actions: stopChild('spawnedId'),
},
stopByRef: {
actions: stopChild(({ context }) => context.spawnedRef),
},
},
});
建模
如果您只需要在响应事件时执行动作,您可以创建一个仅定义了 actions: [ ... ] 的自转换。例如,一个只需要在转换中分配给 context 的机器可能如下所示:
import { createMachine } from 'xstate';
const countMachine = createMachine({
context: {
count: 0,
},
on: {
increment: {
actions: assign({
count: ({ context, event }) => context.count + event.value,
}),
},
decrement: {
actions: assign({
count: ({ context, event }) => context.count - event.value,
}),
},
},
});
简写
对于简单的动作,您可以指定一个动作字符串而不是动作对象。尽管我们更喜欢为了保持一致性使用对象。
import { createMachine } from 'xstate';
const feedbackMachine = createMachine({
// ...
states: {
// ...
question: {
on: {
'feedback.good': {
actions: ['track'],
},
},
},
},
});
动作和 TypeScript
要强类型设置动作类型,请使用 setup({ ... }) 函数,并将动作实现放在 actions: { ... } 对象中。键是动作类型,值是动作函数实现。
您还应该强类型化动作函数的参数,这些参数作为第二个参数传递给动作函数。
import { setup } from 'xstate';
const machine = setup({
actions: {
track: (_, params: { response: string }) => {
// ...
},
increment: (_, params: { value: number }) => {
// ...
},
},
}).createMachine({
// ...
entry: [
{ type: 'track', params: { response: 'good' } },
{ type: 'increment', params: { value: 1 } },
],
});
如果您没有使用 setup({ ... })(强烈推荐),您可以在机器配置的 types.actions 属性中强类型化您的 actions。
const machine = createMachine({
types: {} as {
actions:
| {
type: 'track';
params: {
response: string;
};
}
| { type: 'increment'; params: { value: number } };
},
// ...
entry: [
{ type: 'track', params: { response: 'good' } },
{ type: 'increment', params: { value: 1 } },
],
});
动作速查表
速查表:进入和退出动作
import { createMachine } from 'xstate';
const machine = createMachine({
// 根上的进入动作
entry: [{ type: 'entryAction' }],
exit: [{ type: 'exitAction' }],
initial: 'start',
states: {
start: {
entry: [{ type: 'startEntryAction' }],
exit: [{ type: 'startExitAction' }],
},
},
});
速查表:转换动作
import { createMachine } from 'xstate';
const machine = createMachine({
on: {
someEvent: {
actions: [
{ type: 'doSomething' },
{ type: 'doSomethingElse' },
],
},
},
});
速查表:内联动作函数
import { createMachine } from 'xstate';
const machine = createMachine({
on: {
someEvent: {
actions: [
({ context, event }) => {
console.log(context, event);
},
],
},
},
});
速查表:设置动作
import { setup } from 'xstate';
const someAction = () => {
//...
};
const machine = setup({
actions: {
someAction,
},
}).createMachine({
entry: [
{ type: 'someAction' },
],
// ...
});
速查表:提供动作
import { setup } from 'xstate';
const someAction = () => {
//...
};
const machine = setup({
actions: {
someAction,
},
}).createMachine({
// ...
});
const modifiedMachine = machine.provide({
someAction: () => {
// 覆盖的动作实现
},
});
速查表:assign 动作
使用属性分配器
import { createMachine } from 'xstate';
const countMachine = createMachine({
context: {
count: 0,
},
on: {
increment: {
actions: assign({
count: ({ context, event }) => {
return context.count + event.value;
},
}),
},
},
});
使用函数分配器
import { createMachine } from 'xstate';
const countMachine = createMachine({
context: {
count: 0,
},
on: {
increment: {
actions: assign(({ context, event }) => {
return {
count: context.count + event.value,
};
}),
},
},
});
速查表:raise 动作
import { createMachine, raise } from 'xstate';
const machine = createMachine({
on: {
someEvent: {
actions: raise({ type: 'anotherEvent' }),
},
},
});
速查表:send-to 动作
const machine = createMachine({
on: {
transmit: {
actions: sendTo('someActor', { type: 'someEvent' }),
},
},
});
速查表:排队动作
import { createMachine, enqueueActions } from 'xstate';
const machine = createMachine({
entry: enqueueActions(({ enqueue, check }) => {
enqueue({ type: 'someAction' });
if (check({ type: 'someGuard' })) {
enqueue({ type: 'anotherAction' });
}
enqueue.assign({
count: 0,
});
enqueue.sendTo('someActor', { type: 'someEvent' });
enqueue.raise({ type: 'anEvent' });
}),
});