Перейти к содержанию

XState

XState

Конечные автоматы и диаграммы состояний для современного Интернета.

Новичок в конечных автоматах и диаграмах состояний? Прочтите наше введение.

Пакеты

  • 🤖 xstate - Базовая библиотека конечных автоматов и диаграмм состояний + интерпретатор
  • 🔬 @xstate/fsm - Минимальная библиотека конечных автоматов
  • 📉 @xstate/graph - Утилиты обхода графа для XState
  • ⚛️ @xstate/react - React xуки и утилиты для использования XState в приложениях React
  • 💚 @xstate/vue - Функции композиции и утилиты Vue для использования XState в приложениях Vue
  • 🎷 @xstate/svelte - Утилиты Svelte для использования XState в приложениях Svelte
  • @xstate/test - Утилиты Model-Based-Testing (с использованием XState) для тестирования любого программного обеспечения
  • 🔍 @xstate/inspect - Утилиты разработчика (проверки) для XState

Шаблоны

Начните с создания форка одного из этих шаблонов на CodeSandbox:

Супер быстрый старт

1
npm install xstate
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import { createMachine, interpret } from 'xstate';

// Stateless machine definition
// machine.transition(...) is a pure function used by the interpreter.
const toggleMachine = createMachine({
  id: 'toggle',
  initial: 'inactive',
  states: {
    inactive: {
      on: {
        TOGGLE: { target: 'active' },
      },
    },
    active: {
      on: {
        TOGGLE: { target: 'inactive' },
      },
    },
  },
});

// Machine instance with internal state
const toggleService = interpret(toggleMachine)
  .onTransition((state) => console.log(state.value))
  .start();
// => 'inactive'

toggleService.send({ type: 'TOGGLE' });
// => 'active'

toggleService.send({ type: 'TOGGLE' });
// => 'inactive'

Пример с Promise

📉 See the visualization on stately.ai/viz

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import { createMachine, interpret, assign } from 'xstate';

const fetchMachine = createMachine({
  id: 'Dog API',
  initial: 'idle',
  context: {
    dog: null,
  },
  states: {
    idle: {
      on: {
        FETCH: { target: 'loading' },
      },
    },
    loading: {
      invoke: {
        id: 'fetchDog',
        src: (context, event) =>
          fetch(
            'https://dog.ceo/api/breeds/image/random'
          ).then((data) => data.json()),
        onDone: {
          target: 'resolved',
          actions: assign({
            dog: (_, event) => event.data,
          }),
        },
        onError: {
          target: 'rejected',
        },
      },
      on: {
        CANCEL: { target: 'idle' },
      },
    },
    rejected: {
      on: {
        FETCH: { target: 'loading' },
      },
    },
    resolved: {
      type: 'final',
    },
  },
});

const dogService = interpret(fetchMachine)
  .onTransition((state) => console.log(state.value))
  .start();

dogService.send({ type: 'FETCH' });

Визуализатор

Визуализируйте, моделируйте и делитесь своими диаграммами состояний в XState Viz!

XState Visualizer

Почему?

Диаграммы состояний - это формализм для моделирования реактивных систем с отслеживанием состояния. Это полезно для декларативного описания поведения вашего приложения, от отдельных компонентов до общей логики приложения.

Смотрите 📽 слайды (🎥 видео) или ознакомьтесь с этими ресурсами, чтобы узнать о важности конечных автоматов и диаграмм состояний в пользовательских интерфейсах:

Конечные автоматы

Light Machine

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { createMachine } from 'xstate';

const lightMachine = createMachine({
  id: 'light',
  initial: 'green',
  states: {
    green: {
      on: {
        TIMER: { target: 'yellow' },
      },
    },
    yellow: {
      on: {
        TIMER: { target: 'red' },
      },
    },
    red: {
      on: {
        TIMER: { target: 'green' },
      },
    },
  },
});

const currentState = 'green';

const nextState = lightMachine.transition(currentState, {
  type: 'TIMER',
}).value;

// => 'yellow'

Иерархические (вложенные) конечные автоматы

Hierarchical Light Machine

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import { createMachine } from 'xstate';

const pedestrianStates = {
  initial: 'walk',
  states: {
    walk: {
      on: {
        PED_TIMER: { target: 'wait' },
      },
    },
    wait: {
      on: {
        PED_TIMER: { target: 'stop' },
      },
    },
    stop: {},
  },
};

const lightMachine = createMachine({
  id: 'light',
  initial: 'green',
  states: {
    green: {
      on: {
        TIMER: { target: 'yellow' },
      },
    },
    yellow: {
      on: {
        TIMER: { target: 'red' },
      },
    },
    red: {
      on: {
        TIMER: { target: 'green' },
      },
      ...pedestrianStates,
    },
  },
});

const currentState = 'yellow';

const nextState = lightMachine.transition(currentState, {
  type: 'TIMER',
}).value;
// => {
//   red: 'walk'
// }

lightMachine.transition('red.walk', { type: 'PED_TIMER' })
  .value;
// => {
//   red: 'wait'
// }

объектная нотация для иерархических состояний:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// ...
const waitState = lightMachine.transition(
  { red: 'walk' },
  { type: 'PED_TIMER' }
).value;

// => { red: 'wait' }

lightMachine.transition(waitState, { type: 'PED_TIMER' })
  .value;

// => { red: 'stop' }

lightMachine.transition({ red: 'stop' }, { type: 'TIMER' })
  .value;

// => 'green'

Параллельные конечные автоматы

Parallel state machine

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
import { createMachine } from 'xstate';

const wordMachine = createMachine({
  id: 'word',
  type: 'parallel',
  states: {
    bold: {
      initial: 'off',
      states: {
        on: {
          on: {
            TOGGLE_BOLD: { target: 'off' },
          },
        },
        off: {
          on: {
            TOGGLE_BOLD: { target: 'on' },
          },
        },
      },
    },
    underline: {
      initial: 'off',
      states: {
        on: {
          on: {
            TOGGLE_UNDERLINE: { target: 'off' },
          },
        },
        off: {
          on: {
            TOGGLE_UNDERLINE: { target: 'on' },
          },
        },
      },
    },
    italics: {
      initial: 'off',
      states: {
        on: {
          on: {
            TOGGLE_ITALICS: { target: 'off' },
          },
        },
        off: {
          on: {
            TOGGLE_ITALICS: { target: 'on' },
          },
        },
      },
    },
    list: {
      initial: 'none',
      states: {
        none: {
          on: {
            BULLETS: { target: 'bullets' },
            NUMBERS: { target: 'numbers' },
          },
        },
        bullets: {
          on: {
            NONE: { target: 'none' },
            NUMBERS: { target: 'numbers' },
          },
        },
        numbers: {
          on: {
            BULLETS: { target: 'bullets' },
            NONE: { target: 'none' },
          },
        },
      },
    },
  },
});

const boldState = wordMachine.transition('bold.off', {
  type: 'TOGGLE_BOLD',
}).value;

// {
//   bold: 'on',
//   italics: 'off',
//   underline: 'off',
//   list: 'none'
// }

const nextState = wordMachine.transition(
  {
    bold: 'off',
    italics: 'off',
    underline: 'on',
    list: 'bullets',
  },
  { type: 'TOGGLE_ITALICS' }
).value;

// {
//   bold: 'off',
//   italics: 'on',
//   underline: 'on',
//   list: 'bullets'
// }

История состояний

Конечный автомат с историей состояний

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import { createMachine } from 'xstate';

const paymentMachine = createMachine({
  id: 'payment',
  initial: 'method',
  states: {
    method: {
      initial: 'cash',
      states: {
        cash: {
          on: {
            SWITCH_CHECK: { target: 'check' },
          },
        },
        check: {
          on: {
            SWITCH_CASH: { target: 'cash' },
          },
        },
        hist: { type: 'history' },
      },
      on: {
        NEXT: { target: 'review' },
      },
    },
    review: {
      on: {
        PREVIOUS: { target: 'method.hist' },
      },
    },
  },
});

const checkState = paymentMachine.transition(
  'method.cash',
  {
    type: 'SWITCH_CHECK',
  }
);

// => State {
//   value: { method: 'check' },
//   history: State { ... }
// }

const reviewState = paymentMachine.transition(checkState, {
  type: 'NEXT',
});

// => State {
//   value: 'review',
//   history: State { ... }
// }

const previousState = paymentMachine.transition(
  reviewState,
  {
    type: 'PREVIOUS',
  }
).value;

// => { method: 'check' }

Комментарии