状态节点

状态机包含状态节点(如下所述),它们共同描述状态机可以处于的 整体状态。在下一节描述的 fetchMachine 中,有 状态节点,例如:

  1. // ...
  2. {
  3. states: {
  4. // 状态节点
  5. idle: {
  6. on: {
  7. FETCH: {
  8. target: 'pending';
  9. }
  10. }
  11. }
  12. }
  13. }

以及整体 状态,即 machine.transition() 函数的返回值或 service.onTransition() 的回调值:

  1. const nextState = fetchMachine.transition('pending', { type: 'FULFILL' });
  2. // State {
  3. // value: { success: 'items' },
  4. // actions: [],
  5. // context: undefined,
  6. // ...
  7. // }

什么是状态节点?

在 XState 中,状态节点 指定状态配置。 它们是在状态机的 states 属性上定义的。 同样,子状态节点在状态节点的 states 属性上分层定义。

machine.transition(state, event) 确定的状态,表示状态节点的组合。 例如,在下面的状态机中,有一个 success 状态节点和一个 items 子状态节点。 状态值 { success: 'items' } 表示这些状态节点的组合。

  1. const fetchMachine = createMachine({
  2. id: 'fetch',
  3. // 初始 state
  4. initial: 'idle',
  5. // States
  6. states: {
  7. idle: {
  8. on: {
  9. FETCH: { target: 'pending' }
  10. }
  11. },
  12. pending: {
  13. on: {
  14. FULFILL: { target: 'success' },
  15. REJECT: { target: 'failure' }
  16. }
  17. },
  18. success: {
  19. // 初始子 state
  20. initial: 'items',
  21. // 子 states
  22. states: {
  23. items: {
  24. on: {
  25. 'ITEM.CLICK': { target: 'item' }
  26. }
  27. },
  28. item: {
  29. on: {
  30. BACK: { target: 'items' }
  31. }
  32. }
  33. }
  34. },
  35. failure: {
  36. on: {
  37. RETRY: { target: 'pending' }
  38. }
  39. }
  40. }
  41. });

状态节点类型

有五种不同类型的状态节点:

  • atomic 原子状态节点没有子状态。 (即,它是一个叶节点。)
  • compound 复合状态节点包含一个或多个子 states,并有一个 initial 状态,这是这些子状态之一的 key。
  • parallel 并行状态节点包含两个或多个子 states,并且没有初始状态,因为它表示同时处于其所有子状态。
  • final 最终状态节点是代表抽象“终端”状态的叶节点。
  • history 历史状态节点是一个抽象节点,表示解析到其父节点最近的浅或深历史状态。

可以在状态节点上显式定义状态节点类型:

  1. const machine = createMachine({
  2. id: 'fetch',
  3. initial: 'idle',
  4. states: {
  5. idle: {
  6. type: 'atomic',
  7. on: {
  8. FETCH: { target: 'pending' }
  9. }
  10. },
  11. pending: {
  12. type: 'parallel',
  13. states: {
  14. resource1: {
  15. type: 'compound',
  16. initial: 'pending',
  17. states: {
  18. pending: {
  19. on: {
  20. 'FULFILL.resource1': { target: 'success' }
  21. }
  22. },
  23. success: {
  24. type: 'final'
  25. }
  26. }
  27. },
  28. resource2: {
  29. type: 'compound',
  30. initial: 'pending',
  31. states: {
  32. pending: {
  33. on: {
  34. 'FULFILL.resource2': { target: 'success' }
  35. }
  36. },
  37. success: {
  38. type: 'final'
  39. }
  40. }
  41. }
  42. },
  43. onDone: 'success'
  44. },
  45. success: {
  46. type: 'compound',
  47. initial: 'items',
  48. states: {
  49. items: {
  50. on: {
  51. 'ITEM.CLICK': { target: 'item' }
  52. }
  53. },
  54. item: {
  55. on: {
  56. BACK: { target: 'items' }
  57. }
  58. },
  59. hist: {
  60. type: 'history',
  61. history: 'shallow'
  62. }
  63. }
  64. }
  65. }
  66. });

type 明确指定为 'atomic''compound''parallel''history'、或 'final' 有助于 TypeScript 中的分析和类型检查。 但是,它只需要 parallel、history 和 final 状态。

瞬间状态节点

一个瞬间状态节点是一个“直通”状态节点,它会立即转换到另一个状态节点; 也就是说,状态机不会停留在瞬间状态。 瞬间状态节点可用于,根据条件确定状态机应该从先前状态真正进入哪个状态。 它们与 UML 中的 选择伪状态 最相似。

定义瞬间状态节点的最佳方法是,使用无事件状态和 always 转换。 这是一个转换,其中第一个为 true 的条件会立即被采用。

例如,这个状态机的初始瞬间状态解析为 'morning''afternoon''evening',具体取决于时间(隐藏实现细节):

  1. const timeOfDayMachine = createMachine({
  2. id: 'timeOfDay',
  3. initial: 'unknown',
  4. context: {
  5. time: undefined
  6. },
  7. states: {
  8. // 瞬时 state
  9. unknown: {
  10. always: [
  11. { target: 'morning', cond: 'isBeforeNoon' },
  12. { target: 'afternoon', cond: 'isBeforeSix' },
  13. { target: 'evening' }
  14. ]
  15. },
  16. morning: {},
  17. afternoon: {},
  18. evening: {}
  19. }
  20. }, {
  21. guards: {
  22. isBeforeNoon: // ...
  23. isBeforeSix: // ...
  24. }
  25. });
  26. const timeOfDayService = interpret(timeOfDayMachine.withContext({ time: Date.now() }))
  27. .onTransition(state => console.log(state.value))
  28. .start();
  29. // => 'morning' (假设时间在中午之前)

状态节点元数据

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

```js {19-21,24-26,32-34,37-39,42-44} const fetchMachine = createMachine({ id: ‘fetch’, initial: ‘idle’, states: { idle: { on: { FETCH: { target: ‘loading’ } } }, loading: { after: { 3000: { target: ‘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. 有关用法和更多信息,请参阅状 [状态元数据]($zh-guides-states.md#state-meta-data)。
  5. ## 标签 Tags
  6. 状态节点可以有 **tags**,这些标签是帮助描述状态节点的字符串术语。 标签是可用于对不同状态节点进行分类的元数据。 例如,你可以使用 `"loading"` 标签来表示哪些状态节点代表正在加载数据的状态,并使用 `state.hasTag(tag)` 确定一个状态是否包含那些标记的状态节点:
  7. ```js {10,14}
  8. const machine = createMachine({
  9. initial: 'idle',
  10. states: {
  11. idle: {
  12. on: {
  13. FETCH: 'loadingUser'
  14. }
  15. },
  16. loadingUser: {
  17. tags: ['loading']
  18. // ...
  19. },
  20. loadingFriends: {
  21. tags: ['loading']
  22. // ...
  23. },
  24. editing: {
  25. // ...
  26. }
  27. }
  28. });
  29. machine.initialState.hasTag('loading');
  30. // => false
  31. machine.transition(machine.initialState, 'FETCH').hasTag('loading');
  32. // => true