# 延迟事件 和 转换

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

# 延迟转换

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

const lightDelayMachine = createMachine({
  id: 'lightDelay',
  initial: 'green',
  states: {
    green: {
      after: {
        // 1 秒后,过渡到 yellow
        1000: { target: 'yellow' }
      }
    },
    yellow: {
      after: {
        // 0.5 秒后,过渡到 red
        500: { target: 'red' }
      }
    },
    red: {
      after: {
        // 2 秒后,过渡到 green
        2000: { 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' }
    ];
  }
}
// ...

# 转换的延迟表达式 4.4+

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

const lightDelayMachine = createMachine(
  {
    id: 'lightDelay',
    initial: 'green',
    context: {
      trafficLevel: 'low'
    },
    states: {
      green: {
        after: {
          // 1 秒后,过渡到 yellow
          LIGHT_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 事件将被取消。

# 延迟表达式 4.3+

delay 选项也可以作为延迟表达式求值,它是一个函数,它接收触发 send() 动作的当前 contextevent,并返回已解决的 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);
dynamicDelayService.subscribe({ complete: () => console.log('done!') });
dynamicDelayService.start();

dynamicDelayService.send({
  type: 'ACTIVATE',
  // 任意属性
  wait: 2000
});

// 3000 毫秒(1000 + 2000)后,控制台将记录:
// => 'done!'

# 解释

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

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.0
import { SimulatedClock } from 'xstate/lib/SimulatedClock'; // >= 4.6.0

const 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({ 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 之后 send(...) after(...) 事件,退出状态节点,则将 cancel(...) 那些延迟的 send(...) 事件。