延迟事件 和 转换

可以使用状态图以声明方式处理延迟和超时。 要了解更多信息,请参阅我们的 状态图简介 中的部分。

延迟转换

可以在延迟后自动进行转换。 这在 after 属性中的状态定义中表示,它将毫秒延迟映射到它们的转换:

  1. const lightDelayMachine = createMachine({
  2. id: 'lightDelay',
  3. initial: 'green',
  4. states: {
  5. green: {
  6. after: {
  7. // 1 秒后,过渡到 yellow
  8. 1000: { target: 'yellow' }
  9. }
  10. },
  11. yellow: {
  12. after: {
  13. // 0.5 秒后,过渡到 red
  14. 500: { target: 'red' }
  15. }
  16. },
  17. red: {
  18. after: {
  19. // 2 秒后,过渡到 green
  20. 2000: { target: 'green' }
  21. }
  22. }
  23. }
  24. });

延迟转换的指定方式与你在 on: ... 属性上指定它们的方式相同。 它们可以是明确的:

  1. // ...
  2. states: {
  3. green: {
  4. after: {
  5. 1000: { target: 'yellow' }
  6. }
  7. }
  8. }
  9. // ...

延迟转换也可以是关于单个延迟值的条件:

  1. // ...
  2. states: {
  3. green: {
  4. after: {
  5. 1000: [
  6. { target: 'yellow', cond: 'trafficIsLight' },
  7. { target: 'green' } // 重新进入 'green' 状态
  8. ]
  9. }
  10. }
  11. }
  12. // ...

或者延迟转换可以是多个延迟的条件。 将采用第一个选定的延迟转换,这将防止采用后面的转换。 在以下示例中,如果 'trafficIsLight' 条件为 true,则不会采用后面的 2000: 'yellow' 转换:

  1. // ...
  2. states: {
  3. green: {
  4. after: {
  5. 1000: { target: 'yellow', cond: 'trafficIsLight' },
  6. 2000: { target: 'yellow' } // 始终在 2 秒后转换为“yellow”
  7. }
  8. }
  9. }
  10. // ...

条件延迟转换也可以指定为数组:

  1. // ...
  2. states: {
  3. green: {
  4. after: [
  5. { delay: 1000, target: 'yellow', cond: 'trafficIsLight' },
  6. { delay: 2000, target: 'yellow' }
  7. ];
  8. }
  9. }
  10. // ...

转换的延迟表达式

after: { ... } 属性上指定的延迟转换可以具有动态延迟,由字符串延迟引用指定:

  1. const lightDelayMachine = createMachine(
  2. {
  3. id: 'lightDelay',
  4. initial: 'green',
  5. context: {
  6. trafficLevel: 'low'
  7. },
  8. states: {
  9. green: {
  10. after: {
  11. // 1 秒后,过渡到 yellow
  12. LIGHT_DELAY: { target: 'yellow' }
  13. }
  14. },
  15. yellow: {
  16. after: {
  17. YELLOW_LIGHT_DELAY: { target: 'red' }
  18. }
  19. }
  20. // ...
  21. }
  22. },
  23. {
  24. // 此处配置的字符串延迟
  25. delays: {
  26. LIGHT_DELAY: (context, event) => {
  27. return context.trafficLevel === 'low' ? 1000 : 3000;
  28. },
  29. YELLOW_LIGHT_DELAY: 500 // 静态值
  30. }
  31. }
  32. );

或者直接通过函数,就像条件延迟转换一样:

  1. // ...
  2. green: {
  3. after: [
  4. {
  5. delay: (context, event) => {
  6. return context.trafficLevel === 'low' ? 1000 : 3000;
  7. },
  8. target: 'yellow'
  9. }
  10. ]
  11. },
  12. // ...

但是,更喜欢使用字符串延迟引用,就像第一个示例一样,或者在 delay 属性中:

  1. // ...
  2. green: {
  3. after: [
  4. {
  5. delay: 'LIGHT_DELAY',
  6. target: 'yellow'
  7. }
  8. ]
  9. },
  10. // ...

延迟事件

如果你只想在延迟后发送事件,你可以在 send(...) 动作创建器的第二个参数中指定 delay 作为选项:

  1. import { actions } from 'xstate';
  2. const { send } = actions;
  3. // 1 秒后发送 'TIMER' 事件的动作
  4. const sendTimerAfter1Second = send({ type: 'TIMER' }, { delay: 1000 });

你还可以通过取消这些延迟事件来防止它们被发送。 这是通过cancel(...)动作创建器完成的:

  1. import { actions } from 'xstate';
  2. const { send, cancel } = actions;
  3. // 1 秒后发送 'TIMER' 事件的动作
  4. const sendTimerAfter1Second = send(
  5. { type: 'TIMER' },
  6. {
  7. delay: 1000,
  8. id: 'oneSecondTimer' // 给事件一个唯一的 ID
  9. }
  10. );
  11. const cancelTimer = cancel('oneSecondTimer'); // 传递事件的ID来取消
  12. const toggleMachine = createMachine({
  13. id: 'toggle',
  14. initial: 'inactive',
  15. states: {
  16. inactive: {
  17. entry: sendTimerAfter1Second,
  18. on: {
  19. TIMER: { target: 'active' },
  20. CANCEL: { actions: cancelTimer }
  21. }
  22. },
  23. active: {}
  24. }
  25. });
  26. // 如果 CANCEL 事件在 1 秒之前发送,则 TIMER 事件将被取消。

延迟表达式

delay 选项也可以作为延迟表达式求值,它是一个函数,它接收触发 send() 动作的当前 contextevent,并返回已解决的 delay(以毫秒为单位) ):

  1. const dynamicDelayMachine = createMachine({
  2. id: 'dynamicDelay',
  3. context: {
  4. initialDelay: 1000
  5. },
  6. initial: 'idle',
  7. states: {
  8. idle: {
  9. on: {
  10. ACTIVATE: { target: 'pending' }
  11. }
  12. },
  13. pending: {
  14. entry: send(
  15. { type: 'FINISH' },
  16. {
  17. // 延迟由自定义 event.wait 属性确定
  18. delay: (context, event) => context.initialDelay + event.wait || 0
  19. }
  20. ),
  21. on: {
  22. FINISH: { target: 'finished' }
  23. }
  24. },
  25. finished: { type: 'final' }
  26. }
  27. });
  28. const dynamicDelayService = interpret(dynamicDelayMachine)
  29. .onDone(() => console.log('done!'))
  30. .start();
  31. dynamicDelayService.send({
  32. type: 'ACTIVATE',
  33. // 任意属性
  34. wait: 2000
  35. });
  36. // 3000 毫秒(1000 + 2000)后,控制台将记录:
  37. // => 'done!'

解释

使用 XState 解释,延迟动作将使用原生setTimeoutclearTimeout 函数:

  1. import { interpret } from 'xstate';
  2. const service = interpret(lightDelayMachine).onTransition((state) =>
  3. console.log(state.value)
  4. );
  5. service.start();
  6. // => 'green'
  7. // (1 秒之后)
  8. // => 'yellow'

为了测试,XState 解释提供了一个 SimulatedClock

  1. import { interpret } from 'xstate';
  2. // import { SimulatedClock } from 'xstate/lib/interpreter'; // < 4.6.0
  3. import { SimulatedClock } from 'xstate/lib/SimulatedClock'; // >= 4.6.0
  4. const service = interpret(lightDelayMachine, {
  5. clock: new SimulatedClock()
  6. }).onTransition((state) => console.log(state.value));
  7. service.start();
  8. // => 'green'
  9. // 将 SimulatedClock 向前移动 1 秒
  10. service.clock.increment(1000);
  11. // => 'yellow'

你可以创建自己的“时钟”以提供给解释。 时钟接口是一个具有两个函数/方法的对象:

  • setTimeout - 与 window.setTimeout(fn, timeout) 相同的参数
  • clearTimeout - 与 window.clearTimeout(id) 相同的参数

幕后花絮

after: ... 属性不会为状态图语义引入任何新内容。 相反,它会创建如下所示的正常转换:

  1. // ...
  2. states: {
  3. green: {
  4. entry: [
  5. send(after(1000, 'light.green'), { delay: 1000 }),
  6. send(after(2000, 'light.green'), { delay: 2000 })
  7. ],
  8. onExit: [
  9. cancel(after(1000, 'light.green')),
  10. cancel(after(2000, 'light.green'))
  11. ],
  12. on: {
  13. [after(1000, 'light.green')]: {
  14. target: 'yellow',
  15. cond: 'trafficIsLight'
  16. },
  17. [after(2000, 'light.green')]: {
  18. target: 'yellow'
  19. }
  20. }
  21. }
  22. }
  23. // ...

解释后的状态图将在 delay 之后 send(...) after(...) 事件,退出状态节点,则将 cancel(...) 那些延迟的 send(...) 事件。