Интерпретация автоматов¶
Хотя конечный автомат / диаграмма состояний с чистой функцией .transition()
полезны для обеспечения гибкости, чистоты и тестируемости, но для того, чтобы ее можно было использовать в реальном приложении, нужно еще кое-что:
- Следить за текущим состоянием и сохранять его
- Выполнять побочные эффекты
- Обрабатывать отложенные переходы и события
- Общаться с внешними службами
Интерпретатор (interpreter) отвечает за интерпретацию конечного автомата / диаграммы состояний и выполнение всего вышеперечисленного, то есть его синтаксического анализа и выполнения в среде выполнения. Интерпретируемый, работающий экземпляр диаграммы состояний называется службой (service).
Интерпретатор¶
Начиная с версии 4.0+
Доступен дополнительный интерпретатор, который можно использовать для запуска диаграмм состояний. Интерпретатор, в частности, поддерживает:
- Переходы между состояниями
- Выполнение действий (побочные эффекты)
- Отложенные события с возможностью отмены
- Активности (постоянно выполняемые действия)
- Вызов или создание дочерних служб диаграммы состояний
- Поддержка нескольких слушателей для переходов состояний, изменений контекста, событий и т. д.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Отправка событий¶
События отправляются в работающую службу путем вызова service.send(event)
. Событие можно отправить тремя способами:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
-
- Как объект события (например,
.send({ type: 'CLICK', x: 40, y: 21 })
) -
- Объект события должен иметь строковое свойство
type: ...
.
- Объект события должен иметь строковое свойство
- Как объект события (например,
-
- Как строка (например,
.send('CLICK')
, что равносильно отправке{ type: 'CLICK' }
) -
- Строка представляет тип события.
- Как строка (например,
-
- В виде строки, за которой следует полезная нагрузка в виде объекта (например,
.send('CLICK', { x: 40, y: 21 })
), реализовано начиная с версии 4.5+ -
- Первый строковый аргумент представляет тип события.
-
- Второй аргумент должен быть объектом без свойства
type: ...
- Второй аргумент должен быть объектом без свойства
- В виде строки, за которой следует полезная нагрузка в виде объекта (например,
Внимание
Если служба не инициализирована (то есть, если service.start()
еще не был вызван), события будут отложены до запуска службы. Это означает, что события не будут обрабатываться до тех пор, пока не будет вызван service.start()
, а затем все они будут последовательно обработаны.
Это поведение можно изменить, установив {deferEvents: false}
в параметрах службы. Когда deferEvents
имеет значение false
, отправка события неинициализированной службе вызовет ошибку.
Пакетные события¶
Несколько событий можно отправить в виде группы или «пакета» в работающую службу, вызвав service.send(events)
с массивом событий:
1 2 3 4 5 6 7 8 9 |
|
Это немедленно запланирует последовательную обработку всех пакетных событий. Поскольку каждое событие вызывает переход состояния, который может иметь действия для выполнения, действия в промежуточных состояниях откладываются до тех пор, пока все события не будут обработаны, а затем они выполняются в состоянии, в котором они были созданы (а не в конечном состоянии).
Это означает, что конечное состояние (после обработки всех событий) будет иметь массив .actions
всех накопленных действий из промежуточных состояний. Каждое из этих действий будет связано с соответствующими промежуточными состояниями.
Внимание
Только одно состояние — конечное состояние (т. е. результирующее состояние после обработки всех событий) — будет отправлено слушателю .onTransition(...)
. Это делает пакетные события оптимальным подходом для повышения производительности.
Подсказка
Пакетные события полезны для подходов к поиску событий. Журнал событий может быть сохранен, а затем воспроизведен путем отправки пакетных событий службе для достижения того же состояния.
Переходы¶
Слушатели для переходов между состояниями регистрируются с помощью метода .onTransition(...)
, который принимает прослушиватель состояния. Слушатели состояния вызываются каждый раз, когда происходит переход состояния (включая начальное состояние) с экземпляром текущего состояния:
1 2 3 4 5 6 7 8 9 |
|
Подсказка
Если вы хотите, чтобы обработчики .onTransition (...)
вызывались только при изменении состояния (то есть, когда изменяется state.value
, изменяется state.context
или появляются новые state.actions
), используйте state.changed
:
1 2 3 4 5 |
|
Подсказка
Обратный вызов .onTransition()
не будет выполняться между переходами без событий или другими микрошагами. Он работает только на макрошагах. Микрошаги — это промежуточные переходы между макрошагами.
Запуск и остановка¶
Службу можно инициализировать (т. е. запустить) и остановить с помощью .start()
и .stop()
. Вызов .start()
немедленно переведет службу в исходное состояние. Вызов .stop()
удалит всех слушателей из службы и выполнит любую очистку слушателей, если это возможно.
1 2 3 4 5 6 7 8 9 10 |
|
Службы можно запускать из определенного состояния, передав состояние в service.start(state)
. Это полезно при восстановлении сервиса из ранее сохраненного состояния.
1 2 3 |
|
Выполнение действий¶
Действия (побочные эффекты) по умолчанию выполняются сразу после перехода состояния. Это можно настроить, установив параметр {execute: false}
. Каждый объект действия, указанный в state
, может иметь свойство .exec
, которое вызывается с контекстом состояния context
и объектом события event
.
Действия можно выполнить вручную, вызвав service.execute(state)
. Это полезно, когда вы хотите контролировать выполнение действий:
1 2 3 4 5 6 7 8 9 10 11 |
|
Параметры¶
Следующие параметры могут быть переданы интерпретатору в качестве второго аргумента (interpret(machine, options)
):
execute
(boolean) — Указывает, следует ли выполнять действия состояния при переходе. По-умолчаниюtrue
.-
deferEvents
(boolean) — Указывает, должны ли события, отправленные в неинициализированную службу (т. е. до вызоваservice.start()
), откладываться до инициализации службы. По умолчаниюtrue
.-
- Если
false
, события, отправленные в неинициализированную службу, вызовут ошибку.
- Если
devTools
(boolean) — Указывает, следует ли отправлять события в расширение Redux DevTools. По-умолчанию —false
.logger
— Задает средство ведения журнала, которое будет использоваться для действийlog(...)
. По-умолчанию используется собственный методconsole.log
.clock
— Задает интерфейс часов для отложенных действий. По-умолчанию используются собственные функцииsetTimeout
иclearTimeout
.
Пользовательские интерпретаторы¶
Вы можете использовать любой интерпретатор (или создать свой собственный) для запуска вашего конечного автомата / диаграммы состояний. Вот пример минимальной реализации, демонстрирующий, насколько гибкой может быть интерпретация (несмотря на количество шаблонов):
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 |
|
Примечания¶
- Функция
interpret
экспортируется непосредственно изxstate
начиная с версии 4.3+ (т. е.import { interpret } from 'xstate'
). Для предыдущих версий он импортируется из'xstate/lib/interpreter'
. - Большинство методов интерпретатора можно объединить в цепочку:
1 2 3 4 |
|
- Не вызывайте
service.send(...)
непосредственно из действий. Это затрудняет тестирование, визуализацию и анализ. Вместо этого используйтеinvoke
.