活动 Activities

活动是随时间发生的操作,可以启动和停止。 根据 Harel 的原始状态图论文:

活动总是花费非零时间,例如发出哔哔声、显示或执行冗长的计算。

例如,一个在活动时发出“哔哔”声的开关可以用 'beeping' 活动表示:

  1. const toggleMachine = createMachine(
  2. {
  3. id: 'toggle',
  4. initial: 'inactive',
  5. states: {
  6. inactive: {
  7. on: {
  8. TOGGLE: { target: 'active' }
  9. }
  10. },
  11. active: {
  12. // 只要状态机处于 'active' 状态, 'beeping' 活动就会发生
  13. activities: ['beeping'],
  14. on: {
  15. TOGGLE: { target: 'inactive' }
  16. }
  17. }
  18. }
  19. },
  20. {
  21. activities: {
  22. beeping: () => {
  23. // 开始 beeping activity
  24. const interval = setInterval(() => console.log('BEEP!'), 1000);
  25. // 返回一个函数,用于停止 beeping activity
  26. return () => clearInterval(interval);
  27. }
  28. }
  29. }
  30. );

在 XState 中,活动是在状态节点的 activities 属性上指定的。 当一个状态节点进入时,解释器应该开始它的活动,当它退出时,它应该停止它的活动。

为了确定哪些活动当前处于活动状态,State 有一个 activities 属性,如果活动开始(活动),它是活动名称到 true 的映射,如果活动停止,则映射到 false

  1. const lightMachine = createMachine({
  2. key: 'light',
  3. initial: 'green',
  4. states: {
  5. green: {
  6. on: {
  7. TIMER: { target: 'yellow' }
  8. }
  9. },
  10. yellow: {
  11. on: {
  12. TIMER: { target: 'red' }
  13. }
  14. },
  15. red: {
  16. initial: 'walk',
  17. // 'activateCrosswalkLight' 活动在进入 'light.red' 状态时启动,并在退出时停止。
  18. activities: ['activateCrosswalkLight'],
  19. on: {
  20. TIMER: { target: 'green' }
  21. },
  22. states: {
  23. walk: {
  24. on: {
  25. PED_WAIT: { target: 'wait' }
  26. }
  27. },
  28. wait: {
  29. // 'blinkCrosswalkLight' 活动在进入 'light.red.wait' 状态时启动,并在退出它或其父状态时停止。
  30. activities: ['blinkCrosswalkLight'],
  31. on: {
  32. PED_STOP: { target: 'stop' }
  33. }
  34. },
  35. stop: {}
  36. }
  37. }
  38. }
  39. });

在上面的状态机配置中,当进入 'light.red' 状态时,'activateCrosswalkLight' 将启动。 它还将执行一个特殊的 'xstate.start' 动作,让 服务 知道它应该启动活动:

  1. const redState = lightMachine.transition('yellow', { type: 'TIMER' });
  2. redState.activities;
  3. // => {
  4. // activateCrosswalkLight: true
  5. // }
  6. redState.actions;
  7. // 'activateCrosswalkLight' 活动已启动
  8. // => [
  9. // { type: 'xstate.start', activity: 'activateCrosswalkLight' }
  10. // ]

在同一个父状态内转换将 重新启动它的活动,尽管它可能会启动新的活动:

  1. const redWaitState = lightMachine.transition(redState, { type: 'PED_WAIT' });
  2. redWaitState.activities;
  3. // => {
  4. // activateCrosswalkLight: true,
  5. // blinkCrosswalkLight: true
  6. // }
  7. redWaitState.actions;
  8. // 'blinkCrosswalkLight' 活动已启动
  9. // 注意:“activateCrosswalkLight”活动不会重新启动
  10. // => [
  11. // { type: 'xstate.start', activity: 'blinkCrosswalkLight' }
  12. // ]

离开一个状态将停止其活动:

  1. const redStopState = lightMachine.transition(redWaitState, {
  2. type: 'PED_STOP'
  3. });
  4. redStopState.activities;
  5. // 'blinkCrosswalkLight' 活动已停止
  6. // => {
  7. // activateCrosswalkLight: true,
  8. // blinkCrosswalkLight: false
  9. // }
  10. redStopState.actions;
  11. // 'blinkCrosswalkLight' 活动已停止
  12. // => [
  13. // { type: 'xstate.stop', activity: 'blinkCrosswalkLight' }
  14. // ]

任何停止的活动只会停止一次:

  1. const greenState = lightMachine.transition(redStopState, { type: 'TIMER' });
  2. green.activities;
  3. // 没有激活的活动
  4. // => {
  5. // activateCrosswalkLight: false,
  6. // blinkCrosswalkLight: false
  7. // }
  8. green.actions;
  9. // 'activateCrosswalkLight' 活动已停止
  10. // 注意:'blinkCrosswalkLight' 活动不会再次停止
  11. // => [
  12. // { type: 'xstate.stop', activity: 'activateCrosswalkLight' }
  13. // ]

解释

在状态机选项中,活动的“开始”和“停止”行为可以在 activities 属性中定义。 这是通过以下方式完成的:

  • 传入一个启动活动的函数(作为副作用)
  • 从该函数返回另一个停止活动的函数(也作为副作用)。

例如,下面是一个将 'BEEP!' 打印到控制台每个 context.interval'beeping' 活动是如何实现的:

  1. function createBeepingActivity(context, activity) {
  2. // 开始哔哔活动
  3. const interval = setInterval(() => {
  4. console.log('BEEP!');
  5. }, context.interval);
  6. // 返回一个停止哔哔活动的函数
  7. return () => clearInterval(interval);
  8. }

活动创建者总是被赋予两个参数:

  • 当前context
  • 定义的 activity
    • 例如,{ type: 'beeping' }

然后,你可以将其传递到 activities 属性下的状态机选项(第二个参数)中:

  1. const toggleMachine = createMachine(
  2. {
  3. id: 'toggle',
  4. initial: 'inactive',
  5. context: {
  6. interval: 1000 // 每秒 beep
  7. },
  8. states: {
  9. inactive: {
  10. on: {
  11. TOGGLE: { target: 'active' }
  12. }
  13. },
  14. active: {
  15. activities: ['beeping'],
  16. on: {
  17. TOGGLE: { target: 'inactive' }
  18. }
  19. }
  20. }
  21. },
  22. {
  23. activities: {
  24. beeping: createBeepingActivity
  25. }
  26. }
  27. );

使用XState的解释(interpret),每次发生动作启动一个活动时,都会调用那个活动的创建者来启动该活动,并使用返回的“stopper”(如果返回)来停止 活动:

  1. import { interpret } from 'xstate';
  2. // ... (以前的代码)
  3. const service = interpret(toggleMachine);
  4. service.start();
  5. // 还没有 log
  6. service.send({ type: 'TOGGLE' });
  7. // => 'BEEP!'
  8. // => 'BEEP!'
  9. // => 'BEEP!'
  10. // ...
  11. service.send({ type: 'TOGGLE' });
  12. // 没有更多的哔哔声!

重启 Activities

恢复持久状态 时,默认情况下不会重新启动先前运行的活动。 这是为了防止不良和/或意外行为。 但是,可以通过在重新启动服务之前将 start(...) 操作添加到持久状态来手动启动活动:

  1. import { State, actions } from 'xstate';
  2. // ...
  3. const restoredState = State.create(somePersistedStateJSON);
  4. // 选择要重启的活动
  5. Object.keys(restoredState.activities).forEach((activityKey) => {
  6. if (restoredState.activities[activityKey]) {
  7. // 过滤活动,然后将 start() 动作添加到恢复状态
  8. restoredState.actions.push(actions.start(activityKey));
  9. }
  10. });
  11. // 这将启动 someService 并重新启动活动。
  12. someService.start(restoredState);