@xstate/graph

@xstate/graph package 包含用于 XState 状态机的图形算法和实用程序。

快速开始

  1. 安装 xstate@xstate/graph:
  1. npm install xstate @xstate/graph
  1. 导入图相关方法。例如:
  1. import { createMachine } from 'xstate';
  2. import { getSimplePaths } from '@xstate/graph';
  3. const machine = createMachine(/* ... */);
  4. const paths = getSimplePaths(machine);

API

getShortestPaths(machine, options?)

参数

  • machine - the Machine to traverse
  • options (optional) - options that customize how the algorithm will traverse the machine

Returns the shortest paths (Dijkstra’s algorithm) of a machine from the initial state to every other state as a mapped object, where the:

  • key is the stringified state
  • value is an object with the properties:
    • state - the target State
    • path - the shortest path to get from the initial state to the target state

The path is an array of segments, where each segment is an object with the properties:

  • state - the State of the segment
  • weight - the total weight#Weighted_graph>) of the path
    • Currently, each transition from one state to another has a weight of 1. This will be customizable in the future.
  • event - the event object that transitions the machine from the state to the next state in the path

Every path starts with the initial state.

The overall object structure looks like this:

  1. {
  2. "<SERIALIZED STATE>": {
  3. "state": State { ... },
  4. "path": [
  5. {
  6. "state": State { ... },
  7. "event": { "type": "<event.type>", "<PROP>": "<event.PROP>" }
  8. },
  9. {
  10. "state": State { ... },
  11. "event": { "type": "<event.type>", "<PROP>": "<event.PROP>" }
  12. },
  13. ...
  14. ]
  15. },
  16. ...
  17. }

示例

  1. import { createMachine } from 'xstate';
  2. import { getShortestPaths } from '@xstate/graph';
  3. const feedbackMachine = createMachine({
  4. id: 'feedback',
  5. initial: 'question',
  6. states: {
  7. question: {
  8. on: {
  9. CLICK_GOOD: 'thanks',
  10. CLICK_BAD: 'form',
  11. CLOSE: 'closed',
  12. ESC: 'closed'
  13. }
  14. },
  15. form: {
  16. on: {
  17. SUBMIT: 'thanks',
  18. CLOSE: 'closed',
  19. ESC: 'closed'
  20. }
  21. },
  22. thanks: {
  23. on: {
  24. CLOSE: 'closed',
  25. ESC: 'closed'
  26. }
  27. },
  28. closed: {
  29. type: 'final'
  30. }
  31. }
  32. });
  33. const shortestPaths = getShortestPaths(feedbackMachine);
  34. console.log(shortestPaths);
  35. // => {
  36. // '"question"': {
  37. // state: State { value: 'question', context: undefined },
  38. // weight: 0,
  39. // path: []
  40. // },
  41. // '"thanks"': {
  42. // state: State { value: 'thanks', context: undefined },
  43. // weight: 1,
  44. // path: [
  45. // {
  46. // state: State { value: 'question', context: undefined },
  47. // event: { type: 'CLICK_GOOD' }
  48. // }
  49. // ]
  50. // },
  51. // '"form"': {
  52. // state: State { value: 'form', context: undefined },
  53. // weight: 1,
  54. // path: [
  55. // {
  56. // state: State { value: 'question', context: undefined },
  57. // event: { type: 'CLICK_BAD' }
  58. // }
  59. // ]
  60. // },
  61. // '"closed"': {
  62. // state: State { value: 'closed', context: undefined },
  63. // weight: 1,
  64. // path: [
  65. // {
  66. // state: State { value: 'question', context: undefined },
  67. // event: { type: 'CLOSE' }
  68. // }
  69. // ]
  70. // }
  71. // };

getSimplePaths(machine, options?)

Arguments

  • machine - the Machine to traverse
  • options (optional) - options that customize how the algorithm will traverse the machine

Returns the simple paths#Definitions>) of a machine as a mapped object, where the:

  • key is the stringified state
  • value is an object with the properties:
    • state - the target State
    • paths - the array of paths to get from the initial state to the target state

Each path in paths is an array of segments, where each segment of the path is an object with the properties:

  • state - the State of the segment
  • event - the event object that transitions the machine from the state to the next state in the path

Every path starts with the initial state.

The overall object structure looks like this:

  1. {
  2. "<SERIALIZED STATE>": {
  3. "state": State { ... },
  4. "paths": [
  5. [
  6. {
  7. "state": State { ... },
  8. "event": { "type": "<event.type>", "<PROP>": "<event.PROP>" }
  9. },
  10. {
  11. "state": State { ... },
  12. "event": { "type": "<event.type>", "<PROP>": "<event.PROP>" }
  13. },
  14. ...
  15. ],
  16. ...
  17. ]
  18. },
  19. ...
  20. }

示例

  1. import { createMachine } from 'xstate';
  2. import { getSimplePaths } from '@xstate/graph';
  3. const feedbackMachine = createMachine({
  4. id: 'feedback',
  5. initial: 'question',
  6. states: {
  7. question: {
  8. on: {
  9. CLICK_GOOD: 'thanks',
  10. CLICK_BAD: 'form',
  11. CLOSE: 'closed',
  12. ESC: 'closed'
  13. }
  14. },
  15. form: {
  16. on: {
  17. SUBMIT: 'thanks',
  18. CLOSE: 'closed',
  19. ESC: 'closed'
  20. }
  21. },
  22. thanks: {
  23. on: {
  24. CLOSE: 'closed',
  25. ESC: 'closed'
  26. }
  27. },
  28. closed: {
  29. type: 'final'
  30. }
  31. }
  32. });
  33. const simplePaths = getSimplePaths(feedbackMachine);
  34. console.log(simplePaths);
  35. // => {
  36. // '"question"': {
  37. // state: { value: 'question', context: undefined },
  38. // paths: [[]]
  39. // },
  40. // '"thanks"': {
  41. // state: { value: 'thanks', context: undefined },
  42. // paths: [
  43. // [
  44. // {
  45. // state: { value: 'question', context: undefined },
  46. // event: { type: 'CLICK_GOOD' }
  47. // }
  48. // ],
  49. // [
  50. // {
  51. // state: { value: 'question', context: undefined },
  52. // event: { type: 'CLICK_BAD' }
  53. // },
  54. // {
  55. // state: { value: 'form', context: undefined },
  56. // event: { type: 'SUBMIT' }
  57. // }
  58. // ]
  59. // ]
  60. // },
  61. // '"closed"': {
  62. // state: { value: 'closed', context: undefined },
  63. // paths: [
  64. // [
  65. // {
  66. // state: { value: 'question', context: undefined },
  67. // event: { type: 'CLICK_GOOD' }
  68. // },
  69. // {
  70. // state: { value: 'thanks', context: undefined },
  71. // event: { type: 'CLOSE' }
  72. // }
  73. // ],
  74. // [
  75. // {
  76. // state: { value: 'question', context: undefined },
  77. // event: { type: 'CLICK_GOOD' }
  78. // },
  79. // {
  80. // state: { value: 'thanks', context: undefined },
  81. // event: { type: 'ESC' }
  82. // }
  83. // ],
  84. // ...
  85. // ]
  86. // },
  87. // ...
  88. // };

getPathFromEvents(machine, events)

参数

  • machine - the Machine to traverse
  • events - the sequence of events to generate a path from

Returns a path object with the following keys:

  • state - the target State
  • segments - an array of objects with the following shape:
    • state - the State of the segment
    • event - the event object that transitions the machine from the state to the next state in the path
  1. import { createMachine } from 'xstate';
  2. import { getSimplePaths } from '@xstate/graph';
  3. const feedbackMachine = createMachine({
  4. id: 'feedback',
  5. initial: 'question',
  6. states: {
  7. question: {
  8. on: {
  9. CLICK_GOOD: 'thanks',
  10. CLICK_BAD: 'form',
  11. CLOSE: 'closed',
  12. ESC: 'closed'
  13. }
  14. },
  15. form: {
  16. on: {
  17. SUBMIT: 'thanks',
  18. CLOSE: 'closed',
  19. ESC: 'closed'
  20. }
  21. },
  22. thanks: {
  23. on: {
  24. CLOSE: 'closed',
  25. ESC: 'closed'
  26. }
  27. },
  28. closed: {
  29. type: 'final'
  30. }
  31. }
  32. });
  33. const path = getPathFromEvents(feedbackMachine, [
  34. { type: 'CLICK_GOOD' },
  35. { type: 'SUBMIT' },
  36. { type: 'CLOSE' }
  37. ]);
  38. console.log(path);
  39. // => {
  40. // state: { value: 'closed' },
  41. // segments: [
  42. // {
  43. // state: { value: 'question' },
  44. // event: { type: 'CLICK_GOOD' },
  45. // },
  46. // {
  47. // state: { value: 'form' },
  48. // event: { type: 'SUBMIT' },
  49. // },
  50. // {
  51. // state: { value: 'thanks' },
  52. // event: { type: 'CLOSE' },
  53. // },
  54. // ],
  55. // }

toDirectedGraph(machine)

Converts a machine to a directed graph structure.

Argument Type Description
machine XState Machine created by createMachine(...) The machine to convert to a directed graph structure

示例

  1. import { toDirectedGraph } from '@xstate/graph';
  2. const machine = createMachine({/* ... */});
  3. const digraph = toDirectedGraph(machine);
  4. // returns an object with this structure:
  5. {
  6. id: '...',
  7. stateNode: /* StateNode */,
  8. children: [
  9. { id: '...', children: [/* ... */], edges: [/* ... */] },
  10. { id: '...', /* ... */ },
  11. // ...
  12. ],
  13. edges: [
  14. { source: /* ... */, target: /* ... */, transition: /* ... */ }
  15. // ...
  16. ]
  17. }

选项

Options can be passed into getShortestPaths or getSimplePaths to customize how the graph represented by the machine should be traversed:

  • events - a mapping of event types to an array of event objects to be used for those events
  • filter - a function that determines whether a state should be traversed. If false, the traversal algorithm(s) will assume the state was “seen” and ignore traversing it.

示例

In the below example, the INC event is expanded to include two possible events, with value: 1 and value: 2 as the payload. It also ensures that the state.context.count <= 5; otherwise, this machine would be traversed infinitely.

  1. const counterMachine = createMachine({
  2. id: 'counter',
  3. initial: 'active',
  4. context: { count: 0 },
  5. states: {
  6. active: {
  7. on: {
  8. INC: {
  9. actions: assign({ count: (ctx, e) => ctx.count + e.value })
  10. }
  11. }
  12. }
  13. }
  14. });
  15. const shortestPaths = getShortestPaths(counterMachine, {
  16. events: {
  17. INC: [
  18. { type: 'INC', value: 1 },
  19. { type: 'INC', value: 2 }
  20. ]
  21. },
  22. filter: (state) => state.context.count <= 5
  23. });
  24. console.log(shortestPaths);
  25. // => {
  26. // '"active" | {"count":0}': {
  27. // state: { value: 'active', context: { count: 0 } },
  28. // weight: 0,
  29. // path: []
  30. // },
  31. // '"active" | {"count":1}': {
  32. // state: { value: 'active', context: { count: 1 } },
  33. // weight: 1,
  34. // path: [
  35. // {
  36. // state: { value: 'active', context: { count: 0 } },
  37. // event: { type: 'INC', value: 1 }
  38. // }
  39. // ]
  40. // },
  41. // '"active" | {"count":2}': {
  42. // state: { value: 'active', context: { count: 2 } },
  43. // weight: 1,
  44. // path: [
  45. // {
  46. // state: { value: 'active', context: { count: 0 } },
  47. // event: { type: 'INC', value: 2 }
  48. // }
  49. // ]
  50. // },
  51. // '"active" | {"count":3}': {
  52. // state: { value: 'active', context: { count: 3 } },
  53. // weight: 2,
  54. // path: [
  55. // {
  56. // state: { value: 'active', context: { count: 0 } },
  57. // event: { type: 'INC', value: 1 }
  58. // },
  59. // {
  60. // state: { value: 'active', context: { count: 1 } },
  61. // event: { type: 'INC', value: 2 }
  62. // }
  63. // ]
  64. // },
  65. // ...
  66. // };