延迟事件 和 转换
可以使用状态图以声明方式处理延迟和超时。 要了解更多信息,请参阅我们的 状态图简介 中的部分。
延迟转换
可以在延迟后自动进行转换。 这在 after 属性中的状态定义中表示,它将毫秒延迟映射到它们的转换:
const lightDelayMachine = createMachine({id: 'lightDelay',initial: 'green',states: {green: {after: {// 1 秒后,过渡到 yellow1000: { target: 'yellow' }}},yellow: {after: {// 0.5 秒后,过渡到 red500: { target: 'red' }}},red: {after: {// 2 秒后,过渡到 green2000: { target: 'green' }}}}});
延迟转换的指定方式与你在 on: ... 属性上指定它们的方式相同。 它们可以是明确的:
// ...states: {green: {after: {1000: { target: 'yellow' }}}}// ...
延迟转换也可以是关于单个延迟值的条件:
// ...states: {green: {after: {1000: [{ target: 'yellow', cond: 'trafficIsLight' },{ target: 'green' } // 重新进入 'green' 状态]}}}// ...
或者延迟转换可以是多个延迟的条件。 将采用第一个选定的延迟转换,这将防止采用后面的转换。 在以下示例中,如果 'trafficIsLight' 条件为 true,则不会采用后面的 2000: 'yellow' 转换:
// ...states: {green: {after: {1000: { target: 'yellow', cond: 'trafficIsLight' },2000: { target: 'yellow' } // 始终在 2 秒后转换为“yellow”}}}// ...
条件延迟转换也可以指定为数组:
// ...states: {green: {after: [{ delay: 1000, target: 'yellow', cond: 'trafficIsLight' },{ delay: 2000, target: 'yellow' }];}}// ...
转换的延迟表达式
在 after: { ... } 属性上指定的延迟转换可以具有动态延迟,由字符串延迟引用指定:
const lightDelayMachine = createMachine({id: 'lightDelay',initial: 'green',context: {trafficLevel: 'low'},states: {green: {after: {// 1 秒后,过渡到 yellowLIGHT_DELAY: { target: 'yellow' }}},yellow: {after: {YELLOW_LIGHT_DELAY: { target: 'red' }}}// ...}},{// 此处配置的字符串延迟delays: {LIGHT_DELAY: (context, event) => {return context.trafficLevel === 'low' ? 1000 : 3000;},YELLOW_LIGHT_DELAY: 500 // 静态值}});
或者直接通过函数,就像条件延迟转换一样:
// ...green: {after: [{delay: (context, event) => {return context.trafficLevel === 'low' ? 1000 : 3000;},target: 'yellow'}]},// ...
但是,更喜欢使用字符串延迟引用,就像第一个示例一样,或者在 delay 属性中:
// ...green: {after: [{delay: 'LIGHT_DELAY',target: 'yellow'}]},// ...
延迟事件
如果你只想在延迟后发送事件,你可以在 send(...) 动作创建器的第二个参数中指定 delay 作为选项:
import { actions } from 'xstate';const { send } = actions;// 1 秒后发送 'TIMER' 事件的动作const sendTimerAfter1Second = send({ type: 'TIMER' }, { delay: 1000 });
你还可以通过取消这些延迟事件来防止它们被发送。 这是通过cancel(...)动作创建器完成的:
import { actions } from 'xstate';const { send, cancel } = actions;// 1 秒后发送 'TIMER' 事件的动作const sendTimerAfter1Second = send({ type: 'TIMER' },{delay: 1000,id: 'oneSecondTimer' // 给事件一个唯一的 ID});const cancelTimer = cancel('oneSecondTimer'); // 传递事件的ID来取消const toggleMachine = createMachine({id: 'toggle',initial: 'inactive',states: {inactive: {entry: sendTimerAfter1Second,on: {TIMER: { target: 'active' },CANCEL: { actions: cancelTimer }}},active: {}}});// 如果 CANCEL 事件在 1 秒之前发送,则 TIMER 事件将被取消。
延迟表达式
delay 选项也可以作为延迟表达式求值,它是一个函数,它接收触发 send() 动作的当前 context 和 event,并返回已解决的 delay(以毫秒为单位) ):
const dynamicDelayMachine = createMachine({id: 'dynamicDelay',context: {initialDelay: 1000},initial: 'idle',states: {idle: {on: {ACTIVATE: { target: 'pending' }}},pending: {entry: send({ type: 'FINISH' },{// 延迟由自定义 event.wait 属性确定delay: (context, event) => context.initialDelay + event.wait || 0}),on: {FINISH: { target: 'finished' }}},finished: { type: 'final' }}});const dynamicDelayService = interpret(dynamicDelayMachine).onDone(() => console.log('done!')).start();dynamicDelayService.send({type: 'ACTIVATE',// 任意属性wait: 2000});// 3000 毫秒(1000 + 2000)后,控制台将记录:// => 'done!'
解释
使用 XState 解释,延迟动作将使用原生setTimeout 和 clearTimeout 函数:
import { interpret } from 'xstate';const service = interpret(lightDelayMachine).onTransition((state) =>console.log(state.value));service.start();// => 'green'// (1 秒之后)// => 'yellow'
为了测试,XState 解释提供了一个 SimulatedClock:
import { interpret } from 'xstate';// import { SimulatedClock } from 'xstate/lib/interpreter'; // < 4.6.0import { SimulatedClock } from 'xstate/lib/SimulatedClock'; // >= 4.6.0const service = interpret(lightDelayMachine, {clock: new SimulatedClock()}).onTransition((state) => console.log(state.value));service.start();// => 'green'// 将 SimulatedClock 向前移动 1 秒service.clock.increment(1000);// => 'yellow'
你可以创建自己的“时钟”以提供给解释。 时钟接口是一个具有两个函数/方法的对象:
setTimeout- 与window.setTimeout(fn, timeout)相同的参数clearTimeout- 与window.clearTimeout(id)相同的参数
幕后花絮
after: ... 属性不会为状态图语义引入任何新内容。 相反,它会创建如下所示的正常转换:
// ...states: {green: {entry: [send(after(1000, 'light.green'), { delay: 1000 }),send(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 之后 send(...) after(...) 事件,退出状态节点,则将 cancel(...) 那些延迟的 send(...) 事件。
