状态 State

状态是系统(例如应用)在特定时间点的抽象表示。 要了解更多信息,请阅读 状态图简介中的状态部分

API

状态机的当前状态由一个 State 实例表示:

```js {13-18,21-26} const lightMachine = createMachine({ id: ‘light’, initial: ‘green’, states: { green: { // } // … } });

console.log(lightMachine.initialState); // State { // value: ‘green’, // actions: [], // context: undefined, // // … // }

console.log(lightMachine.transition(‘yellow’, { type: ‘TIMER’ })); // State { // value: { red: ‘walk’ }, // actions: [], // context: undefined, // // … // }

  1. ## State 定义
  2. `State` 对象实例是 JSON 可序列化的,并具有以下属性:
  3. - `value` - 当前状态的值。(例如, `{red: 'walk'}`)
  4. - `context` - 当前状态的 [context](./context.md)
  5. - `event` - 触发转换到此状态的事件对象
  6. - `actions` - 要执行的 [动作](./actions.md) 数组
  7. - `activities` - 如果 [活动](./activities.md) 开始,则活动映射到 `true`,如果活动停止,则映射到 `false`
  8. - `history` - 上一个 `State` 实例
  9. - `meta` - [状态节点]($zh-guides-statenodes.md) 的元属性上定义的任何静态元数据
  10. - `done` - 状态是否表示最终状态
  11. `State` 对象还包含其他属性,例如 `historyValue``events``tree` 和其他通常不相关并在内部使用的属性。
  12. ## State 方法和属性
  13. 你可以使用一些有用的方法和属性来获得更好的开发体验:
  14. ### `state.matches(parentStateValue)`
  15. `state.matches(parentStateValue)` 方法确定当前 `state.value` 是否是给定 `parentStateValue` 的子集。 该方法确定父状态值是否“匹配”状态值。 例如,假设当前 `state.value` `{ red: 'stop' }`
  16. ```js
  17. console.log(state.value);
  18. // => { red: 'stop' }
  19. console.log(state.matches('red'));
  20. // => true
  21. console.log(state.matches('red.stop'));
  22. // => true
  23. console.log(state.matches({ red: 'stop' }));
  24. // => true
  25. console.log(state.matches('green'));
  26. // => false

::: tip 如果要匹配多个状态中的一个,可以在状态值数组上使用 .some() 来完成此操作:

  1. const isMatch = [{ customer: 'deposit' }, { customer: 'withdrawal' }].some(
  2. state.matches
  3. );

:::

state.nextEvents

state.nextEvents 指定将导致从当前状态转换的下一个事件:

  1. const { initialState } = lightMachine;
  2. console.log(initialState.nextEvents);
  3. // => ['TIMER', 'EMERGENCY']

state.nextEvents 在确定可以采取哪些下一个事件,以及在 UI 中表示这些潜在事件(例如启用/禁用某些按钮)方面很有用。

state.changed

state.changed 指定此 state 是否已从先前状态更改。 在以下情况下,状态被视为“已更改”:

  • 它的值不等于它之前的值,或者:
  • 它有任何新动作(副作用)要执行。

初始状态(没有历史记录)将返回 undefined

  1. const { initialState } = lightMachine;
  2. console.log(initialState.changed);
  3. // => undefined
  4. const nextState = lightMachine.transition(initialState, { type: 'TIMER' });
  5. console.log(nextState.changed);
  6. // => true
  7. const unchangedState = lightMachine.transition(nextState, {
  8. type: 'UNKNOWN_EVENT'
  9. });
  10. console.log(unchangedState.changed);
  11. // => false

state.done

state.done 指定 state 是否为“最终状态” - 最终状态是指示其状态机已达到其最终状态,并且不能再转换到任何其他状态的状态。

  1. const answeringMachine = createMachine({
  2. initial: 'unanswered',
  3. states: {
  4. unanswered: {
  5. on: {
  6. ANSWER: { target: 'answered' }
  7. }
  8. },
  9. answered: {
  10. type: 'final'
  11. }
  12. }
  13. });
  14. const { initialState } = answeringMachine;
  15. initialState.done; // false
  16. const answeredState = answeringMachine.transition(initialState, {
  17. type: 'ANSWER'
  18. });
  19. answeredState.done; // true

state.toStrings()

state.toStrings() 方法返回表示所有状态值路径的字符串数组。 例如,假设当前 state.value{ red: 'stop' }

  1. console.log(state.value);
  2. // => { red: 'stop' }
  3. console.log(state.toStrings());
  4. // => ['red', 'red.stop']

state.toStrings() 方法对于表示基于字符串的环境中的当前状态非常有用,例如在 CSS 类或数据属性中。

state.children

state.children 是将生成的 服务/演员 ID 映射到其实例的对象。 详情 📖 参考服务

使用 state.children 示例

  1. const machine = createMachine({
  2. // ...
  3. invoke: [
  4. { id: 'notifier', src: createNotifier },
  5. { id: 'logger', src: createLogger }
  6. ]
  7. // ...
  8. });
  9. const service = invoke(machine)
  10. .onTransition((state) => {
  11. state.children.notifier; // service 来自 createNotifier()
  12. state.children.logger; // service 来自 createLogger()
  13. })
  14. .start();

state.hasTag(tag)

从 4.19.0 开始

state.hasTag(tag) 方法,当前状态配置是否具有给定标签的状态节点。

```js {5,8,11} const machine = createMachine({ initial: ‘green’, states: { green: { tags: ‘go’ // 单标签 }, yellow: { tags: ‘go’ }, red: { tags: [‘stop’, ‘other’] // 多标签 } } });

  1. 例如,如果上面的状态机处于 `green` `yellow` 状态,而不是直接使用 `state.matches('green') || state.matches('yellow')`,可以使用 `state.hasTag('go')`
  2. ```js
  3. const canGo = state.hasTag('go');
  4. // => 如果在 'green' 或 'yellow' 状态,返回 `true`

state.can(event)

从 4.25.0 开始

state.can(event) 方法确定一个 event 在发送到解释的(interpret)状态机时,是否会导致状态改变。 如果状态因发送 event 而改变,该方法将返回 true; 否则该方法将返回 false

  1. const machine = createMachine({
  2. initial: 'inactive',
  3. states: {
  4. inactive: {
  5. on: {
  6. TOGGLE: 'active'
  7. }
  8. },
  9. active: {
  10. on: {
  11. DO_SOMETHING: { actions: ['something'] }
  12. }
  13. }
  14. }
  15. });
  16. const inactiveState = machine.initialState;
  17. inactiveState.can('TOGGLE'); // true
  18. inactiveState.can('DO_SOMETHING'); // false
  19. // 还接收完整的 event 对象:
  20. inactiveState.can({
  21. type: 'DO_SOMETHING',
  22. data: 42
  23. }); // false
  24. const activeState = machine.transition(inactiveState, 'TOGGLE');
  25. activeState.can('TOGGLE'); // false
  26. activeState.can('DO_SOMETHING'); // true, 因为一个 action 将被执行

如果 state.changedtrue,并且以下任何一项为 true,则状态被视为“changed”:

  • state.value 改变
  • 有新的 state.actions 需要执行
  • state.context 改变

持久化 State

如前所述,可以通过将 State 对象序列化为字符串 JSON 格式来持久化它:

  1. const jsonState = JSON.stringify(currentState);
  2. // 例如: 持久化到 localStorage
  3. try {
  4. localStorage.setItem('app-state', jsonState);
  5. } catch (e) {
  6. // 不能保存 localStorage
  7. }

可以使用静态 State.create(...) 方法恢复状态:

  1. import { State, interpret } from 'xstate';
  2. import { myMachine } from '../path/to/myMachine';
  3. // 从 localStorage 检索状态定义,如果 localStorage 为空,则使用状态机的初始状态
  4. const stateDefinition =
  5. JSON.parse(localStorage.getItem('app-state')) || myMachine.initialState;
  6. // 使用 State.create() 从普通对象恢复状态
  7. const previousState = State.create(stateDefinition);

然后,你可以通过将 State 传递到已解释的服务的 .start(...) 方法,来从此状态解释状态机:

  1. // ...
  2. // 这将在指定的状态启动 service
  3. const service = interpret(myMachine).start(previousState);

这还将维护和恢复以前的 历史状态,并确保 .events.nextEvents 代表正确的值。

::: warning XState 尚不支持持久化生成的 演员(actors) :::

State 元数据

元数据,是描述任何 状态节点 相关属性的静态数据,可以在状态节点的 .meta 属性上指定:

```js {17-19,22-24,30-32,35-37,40-42} const fetchMachine = createMachine({ id: ‘fetch’, initial: ‘idle’, states: { idle: { on: { FETCH: { target: ‘loading’ } } }, loading: { after: { 3000: ‘failure.timeout’ }, on: { RESOLVE: { target: ‘success’ }, REJECT: { target: ‘failure’ }, TIMEOUT: { target: ‘failure.timeout’ } // 手动超时 }, meta: { message: ‘Loading…’ } }, success: { meta: { message: ‘The request succeeded!’ } }, failure: { initial: ‘rejection’, states: { rejection: { meta: { message: ‘The request failed.’ } }, timeout: { meta: { message: ‘The request timed out.’ } } }, meta: { alert: ‘Uh oh.’ } } } });

  1. 状态机的当前状态,收集所有状态节点的 `.meta` 数据,由状态值表示,并将它们放在一个对象上,其中:
  2. - key [状态节点 ID](./ids.md)
  3. - value 是状态节点 `.meta` 的值
  4. 例如,如果上述状态机处于 `failure.timeout` 状态(由 ID `“failure”` `“failure.timeout”` 的两个状态节点表示),则 `.meta` 属性将组合所有 `.meta` 值,如下所示:
  5. ```js {4-11}
  6. const failureTimeoutState = fetchMachine.transition('loading', {
  7. type: 'TIMEOUT'
  8. });
  9. console.log(failureTimeoutState.meta);
  10. // => {
  11. // failure: {
  12. // alert: 'Uh oh.'
  13. // },
  14. // 'failure.timeout': {
  15. // message: 'The request timed out.'
  16. // }
  17. // }

::: tip 提示:聚合元数据 你如何处理元数据取决于你。 理想情况下,元数据应 包含 JSON 可序列化值。 考虑以不同方式合并/聚合元数据。 例如,以下函数丢弃状态节点 ID key(如果它们不相关)并合并元数据:

  1. function mergeMeta(meta) {
  2. return Object.keys(meta).reduce((acc, key) => {
  3. const value = meta[key];
  4. // 假设每个元值都是一个对象
  5. Object.assign(acc, value);
  6. return acc;
  7. }, {});
  8. }
  9. const failureTimeoutState = fetchMachine.transition('loading', {
  10. type: 'TIMEOUT'
  11. });
  12. console.log(mergeMeta(failureTimeoutState.meta));
  13. // => {
  14. // alert: 'Uh oh.',
  15. // message: 'The request timed out.'
  16. // }

:::

笔记

  • 你永远不必手动创建 State 实例。 将 State 视为仅来自 machine.transition(...)service.onTransition(...) 的只读对象。
  • state.history 不会保留其历史记录以防止内存泄漏。state.history.history === undefined。 否则,你最终会创建一个巨大的链表并重新发明区块链,而我们并不这样做。
    • 此行为可能会在未来版本中进行配置。