# 延迟事件和转换

¥Delayed events and transitions

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

¥Delays and timeouts can be handled declaratively with statecharts. To learn more, see the section in our introduction to statecharts.

# 延迟转换

¥Delayed transitions

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

¥Transitions can be taken automatically after a delay. This is represented in a state definition in the after property, which maps millisecond delays to their transitions:

const lightDelayMachine = createMachine({
  id: 'lightDelay',
  initial: 'green',
  states: {
    green: {
      after: {
        // after 1 second, transition to yellow
        1000: { target: 'yellow' }
      }
    },
    yellow: {
      after: {
        // after 0.5 seconds, transition to red
        500: { target: 'red' }
      }
    },
    red: {
      after: {
        // after 2 seconds, transition to green
        2000: { target: 'green' }
      }
    }
  }
});

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

¥Delayed transitions can be specified in the same way that you specify them on the on: ... property. They can be explicit:

// ...
states: {
  green: {
    after: {
      1000: { target: 'yellow' }
    }
  }
}
// ...

延迟转换也可以以单个延迟值为条件:

¥Delayed transitions can also be conditional with regard to a single delay value:

// ...
states: {
  green: {
    after: {
      1000: [
        { target: 'yellow', cond: 'trafficIsLight' },
        { target: 'green' } // reenter 'green' state
      ]
    }
  }
}
// ...

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

¥Or delayed transitions can be conditional for multiple delays. The first selected delayed transition will be taken, which will prevent later transitions from being taken. In the following example, if the 'trafficIsLight' condition is true, then the later 2000: 'yellow' transition will not be taken:

// ...
states: {
  green: {
    after: {
      1000: { target: 'yellow', cond: 'trafficIsLight' },
      2000: { target: 'yellow' } // always transition to 'yellow' after 2 seconds
    }
  }
}
// ...

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

¥Conditional delayed transitions can also be specified as an array:

// ...
states: {
  green: {
    after: [
      { delay: 1000, target: 'yellow', cond: 'trafficIsLight' },
      { delay: 2000, target: 'yellow' }
    ];
  }
}
// ...

# 转场时的延迟表达式 4.4+

¥Delay expressions on transitions 4.4+

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

¥Delayed transitions specified on the after: { ... } property can have dynamic delays, specified either by a string delay reference:

const lightDelayMachine = createMachine(
  {
    id: 'lightDelay',
    initial: 'green',
    context: {
      trafficLevel: 'low'
    },
    states: {
      green: {
        after: {
          // after 1 second, transition to yellow
          LIGHT_DELAY: { target: 'yellow' }
        }
      },
      yellow: {
        after: {
          YELLOW_LIGHT_DELAY: { target: 'red' }
        }
      }
      // ...
    }
  },
  {
    // String delays configured here
    delays: {
      LIGHT_DELAY: (context, event) => {
        return context.trafficLevel === 'low' ? 1000 : 3000;
      },
      YELLOW_LIGHT_DELAY: 500 // static value
    }
  }
);

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

¥Or directly by a function, just like conditional delayed transitions:

// ...
green: {
  after: [
    {
      delay: (context, event) => {
        return context.trafficLevel === 'low' ? 1000 : 3000;
      },
      target: 'yellow'
    }
  ]
},
// ...

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

¥However, prefer using string delay references, just like the first example, or in the delay property:

// ...
green: {
  after: [
    {
      delay: 'LIGHT_DELAY',
      target: 'yellow'
    }
  ]
},
// ...

# 延迟事件

¥Delayed events

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

¥If you just want to send an event after a delay, you can specify the delay as an option in the second argument of the send(...) action creator:

import { actions } from 'xstate';
const { send } = actions;

// action to send the 'TIMER' event after 1 second
const sendTimerAfter1Second = send({ type: 'TIMER' }, { delay: 1000 });

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

¥You can also prevent those delayed events from being sent by canceling them. This is done with the cancel(...) action creator:

import { actions } from 'xstate';
const { send, cancel } = actions;

// action to send the 'TIMER' event after 1 second
const sendTimerAfter1Second = send(
  { type: 'TIMER' },
  {
    delay: 1000,
    id: 'oneSecondTimer' // give the event a unique ID
  }
);

const cancelTimer = cancel('oneSecondTimer'); // pass the ID of event to cancel

const toggleMachine = createMachine({
  id: 'toggle',
  initial: 'inactive',
  states: {
    inactive: {
      entry: sendTimerAfter1Second,
      on: {
        TIMER: { target: 'active' },
        CANCEL: { actions: cancelTimer }
      }
    },
    active: {}
  }
});

// if the CANCEL event is sent before 1 second, the TIMER event will be canceled.

# 延迟表达式 4.3+

¥Delay Expressions 4.3+

delay 选项也可以被计算为延迟表达式,它是一个函数,它接受触发 send() 操作的当前 contextevent,并返回解析后的 delay(以毫秒为单位):

¥The delay option can also be evaluated as a delay expression, which is a function that takes in the current context and event that triggered the send() action, and returns the resolved delay (in milliseconds):

const dynamicDelayMachine = createMachine({
  id: 'dynamicDelay',
  context: {
    initialDelay: 1000
  },
  initial: 'idle',
  states: {
    idle: {
      on: {
        ACTIVATE: { target: 'pending' }
      }
    },
    pending: {
      entry: send(
        { type: 'FINISH' },
        {
          // delay determined from custom event.wait property
          delay: (context, event) => context.initialDelay + event.wait || 0
        }
      ),
      on: {
        FINISH: { target: 'finished' }
      }
    },
    finished: { type: 'final' }
  }
});

const dynamicDelayService = interpret(dynamicDelayMachine);
dynamicDelayService.subscribe({ complete: () => console.log('done!') });
dynamicDelayService.start();

dynamicDelayService.send({
  type: 'ACTIVATE',
  // arbitrary property
  wait: 2000
});

// after 3000ms (1000 + 2000), console will log:
// => 'done!'

# 解释

¥Interpretation

对于 XState interpreter,延迟操作将使用 nativesetTimeoutclearTimeout 函数:

¥With the XState interpreter, delayed actions will use the nativesetTimeout and clearTimeout functions:

import { interpret } from 'xstate';

const service = interpret(lightDelayMachine).onTransition((state) =>
  console.log(state.value)
);

service.start();
// => 'green'

// (after 1 second)

// => 'yellow'

为了测试,XState 解释器提供了 SimulatedClock

¥For testing, the XState interpreter provides a SimulatedClock:

import { interpret } from 'xstate';
// import { SimulatedClock } from 'xstate/lib/interpreter'; // < 4.6.0
import { SimulatedClock } from 'xstate/lib/SimulatedClock'; // >= 4.6.0

const simulatedClock = new SimulatedClock();
const service = interpret(lightDelayMachine, {
  clock: simulatedClock
}).onTransition((state) => console.log(state.value));

service.start();
// => 'green'

// move the SimulatedClock forward by 1 second
simulatedClock.increment(1000);
// => 'yellow'

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

¥You can create your own “clock” to provide to the interpreter. The clock interface is an object with two functions/methods:

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

    ¥setTimeout - same arguments as window.setTimeout(fn, timeout)

  • clearTimeout - 与 window.clearTimeout(id) 相同的参数

    ¥clearTimeout - same arguments as window.clearTimeout(id)

# 幕后

¥Behind the scenes

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

¥The after: ... property does not introduce anything new to statechart semantics. Instead, it creates normal transitions that look like this:

// ...
states: {
  green: {
    entry: [
      send({ type: after(1000, 'light.green') }, { delay: 1000 }),
      send({ type: after(2000, 'light.green') }, { delay: 2000 })
    ],
    onExit: [
      cancel(after(1000, 'light.green')),
      cancel(after(2000, 'light.green'))
    ],
    on: {
      [after(1000, 'light.green')]: {
        target: 'yellow',
        cond: 'trafficIsLight'
      },
      [after(2000, 'light.green')]: {
        target: 'yellow'
      }
    }
  }
}
// ...

解释的状态图将在 delay 后对 after(...) 事件进行 send(...),除非退出状态节点,这将对那些延迟的 send(...) 事件进行 cancel(...)

¥The interpreted statechart will send(...) the after(...) events after their delay, unless the state node is exited, which will cancel(...) those delayed send(...) events.