Skip to content

Actions

动作是一次性执行的效果。当状态机转换时,它可能会执行动作。动作响应事件发生,通常在转换中的 actions: [...] 属性中定义。动作也可以在进入状态的任何转换中定义在状态的 entry: [...] 属性中,或者在退出状态的任何转换中定义在状态的 exit: [...] 属性中。

动作也可以在状态的 entryexit 上定义,可以是单个动作或数组。

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 属性中动态传递参数。该函数接受一个包含当前 contextevent 的对象作为参数。

import { setup } from 'xstate';

const feedbackMachine = setup({
actions: {
logInitialRating: (_, params: { initialRating: number }) => {
// ...
},
},
}).createMachine({
context: {
initialRating: 3,
},
entry: [
{
type: 'logInitialRating',
params: ({ context }) => ({
initialRating: context.initialRating,
}),
},
],
});

这是使动作更具可重用性的推荐方法,因为您可以定义不依赖于机器的 contextevent 类型的动作。

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',
}),
},
},
});

其他选项,例如 delayid,可以作为第三个参数传递:

const machine = createMachine({
on: {
transmit: {
actions: sendTo(
'someActor',
{ type: 'someEvent' },
{
id: 'transmission',
delay: 1000,
},
),
},
},
});

延迟动作可以通过它们的 id 取消。请参阅 cancel(...)

Send-parent 动作

sendParent(...) 动作是一种特殊的动作,用于向父 actor 发送事件(如果存在父 actor)。

排队动作

enqueueActions(...) 动作创建器是一个高级动作,它将动作按顺序排队执行,而不实际执行任何动作。它接受一个回调,该回调接收 contextevent 以及 enqueuecheck 函数:

  • 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' });
}),
});