События и переходы
Переход (transition) — это изменение одного конечного состояния на другое, инициируемое событием.
Событие (event) — это сигнал, триггер или сообщение, вызывающее переход. Когда актор получает событие, его машина определяет, есть ли в текущем состоянии активные переходы для этого события. Если активные переходы существуют, машина выполнит их и выполнит связанные с ними действия.
Переходы являются «детерминированными»; каждая комбинация состояния и события всегда указывает на одно и то же следующее состояние. Когда машина состояний получает событие, только активные конечные состояния проверяются на наличие перехода для этого события. Такие переходы называются активными переходами. Если есть активный переход, машина состояний выполнит действия перехода, а затем перейдёт в целевое состояние.
Переходы представлены с помощью on: в состоянии:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Объекты событий¶
В XState события представлены объектами событий со свойством type и необязательной полезной нагрузкой:
- Свойство
type— это строка, представляющая тип события. - Полезная нагрузка — это объект, содержащий дополнительные данные о событии.
1 2 3 4 5 6 7 | |
Выбор переходов¶
Переходы выбираются путём проверки сначала самых глубоких дочерних состояний. Если переход активен (то есть, если его защита проходит), он будет выполнен. Если нет, будет проверено родительское состояние и так далее.
- Начните с самых глубоких активных узлов состояния (также известных как атомарные узлы состояния)
- Если переход активен (нет
guardили егоguardвычисляется какtrue), выберите его. - Если ни один переход не активен, перейдите к родительскому узлу состояния и повторите шаг 1.
- Наконец, если ни один переход не активен, переходы не будут выполнены, и состояние не изменится.
Самопереходы¶
Состояние может переходить в себя. Это называется самопереход и полезно для изменения контекста и/или выполнения действий без изменения конечного состояния. Вы также можете использовать самопереходы для перезапуска состояния.
Корневые самопереходы:
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Самопереходы в состояниях:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
Переходы между состояниями¶
Обычно переходы происходят между двумя соседними состояниями. Эти переходы определяются путём установки target в качестве ключа соседнего состояния.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
Переходы от родителя к потомку¶
Когда актор машины состояний получает событие, он сначала проверяет самое глубокое (атомарное) состояние, чтобы узнать, есть ли активный переход. Если нет, проверяется родительское состояние и так далее, пока машина состояний не достигнет корневого состояния.
Когда вы хотите, чтобы событие переходило в состояние независимо от того, какое соседнее состояние активно, полезным паттерном является переход от родительского состояния к дочернему.
Например, приведённая ниже машина состояний перейдёт в состояние colorMode.system при событии mode.reset независимо от того, в каком состоянии она находится в данный момент.
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 | |
Повторный вход¶
По умолчанию, когда машина состояний переходит из некоторого состояния в то же самое состояние или из родительского состояния в потомка (дочернее, внучатое и т.д.) этого родительского состояния, она не выполняет повторный вход в состояние; то есть она не будет выполнять действия exit и entry родительского состояния. Она не остановит существующих вызванных акторов и не запустит новых.
Это можно изменить с помощью свойства перехода reenter: если вы хотите, чтобы родительское состояние было введено повторно, вы можете установить reenter: true. Это приведёт к повторному входу в состояние при переходе к себе или состояниям-потомкам, выполняя действия exit и entry состояния. Это остановит существующих вызванных акторов и запустит новых.
Совет
В XState v4 переходы с повторным входом были известны как внешние переходы, а переходы по умолчанию — как внутренние переходы.
Самопереходы с reenter: true:
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 | |
Переходы родитель-потомок с reenter: true:
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 | |
Переходы в любое состояние¶
Соседние состояния-потомки: { target: 'sibling.child.grandchild' }
Родительские к потомкам: { target: '.child.grandchild' }
Состояние в любое состояние: { target: '#specificState' }
Запрещённые переходы¶
{ on: { forbidden: {} } }- Отличается от пропуска перехода; алгоритм выбора перехода прекратит поиск
- То же самое, что
{ on: { forbidden: { target: undefined } } }
Переходы с подстановочными знаками¶
Переход с подстановочным знаком — это переход, который соответствует любому событию. Дескриптор события (ключ объекта on: {...}) определяется с использованием подстановочного символа * в качестве типа события:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Переходы с подстановочными знаками полезны для:
- обработки событий, которые не обрабатываются никаким другим переходом.
- в качестве перехода «на все случаи», который обрабатывает любое событие в состоянии.
Переход с подстановочным знаком имеет наименьший приоритет; он будет выполнен только в том случае, если никакие другие переходы не активны.
Частичные переходы с подстановочными знаками¶
Частичный переход с подстановочным знаком — это переход, который соответствует любому событию, начинающемуся с определённого префикса. Дескриптор события определяется путём использования подстановочного символа (*) после точки (.) в качестве типа события:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
Подстановочный символ (*) может использоваться только в суффиксе дескриптора события после точки (.):
Допустимые примеры с подстановочными знаками¶
- ✅
mouse.*: соответствуетmouse,mouse.click,mouse.moveи т.д. - ✅
mouse.click.*: соответствуетmouse.click,mouse.click.left,mouse.click.rightи т.д.
Недопустимые подстановочные знаки¶
- 🚫
: недопустимо; не соответствует никакому событию.mouse* - 🚫
: недопустимо;mouse.*.click*нельзя использовать в середине дескриптора события. - 🚫
: недопустимо;*.click*нельзя использовать в префиксе дескриптора события. - 🚫
: недопустимо; не соответствует никакому событию.mouse.click* - 🚫
: недопустимо;mouse.*.**нельзя использовать в середине дескриптора события.
Множественные переходы в параллельных состояниях¶
Поскольку параллельные состояния имеют несколько регионов, которые могут быть активны одновременно, возможно, чтобы несколько переходов были активны одновременно. В этом случае все активные переходы в эти регионы будут выполнены.
Множественные цели указываются как массив строк:
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 | |
В этом примере:
- Событие
set.dark.customодновременно переведёт оба региона: регионmodeвdarkи регионthemeвcustom - Событие
set.lightпереведёт только регионmodeвlight, оставив регионthemeв текущем состоянии - Каждый регион всё ещё может управляться независимо через свои собственные события (
toggleиchange)
Другие переходы¶
- Безсобытийные (always) переходы — это переходы без событий. Эти переходы всегда выполняются после любого перехода в их состоянии, который активен.
- Отложенные (after) переходы — это переходы, которые активируются после указанной продолжительности.
Описания переходов¶
Вы можете добавить строку .description к переходу, чтобы описать его. Это полезно для объяснения назначения перехода в визуализированной машине состояний.
1 2 3 4 5 6 7 8 9 10 11 | |
Сокращения¶
Если переход указывает только target, то строковая цель может использоваться как сокращение вместо всего объекта перехода:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
Использование сокращения строковой цели полезно для быстрого прототипирования машин состояний. В целом, мы рекомендуем использовать полный синтаксис объекта перехода, так как он будет согласован со всеми другими объектами перехода и будет легче добавлять действия, защиты и другие свойства к переходу в будущем.
TypeScript¶
TypeScript
XState v5 требует TypeScript версии 5.0 или выше.
Для лучших результатов используйте последнюю версию TypeScript. Подробнее о XState и TypeScript
Переходы в основном используют тип события, которым они активируются.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
Часто задаваемые вопросы¶
Как я могу прослушивать события, отправляемые акторам?
Вы можете использовать API инспекции для прослушивания всех событий инспекции в системе акторов. Событие инспекции @xstate.event содержит информацию о событиях, отправляемых от одного актора к другому (или самому себе):
1 2 3 4 5 6 7 8 9 10 11 | |
Шпаргалка по переходам¶
Используйте нашу шпаргалку по событиям и переходам XState ниже для быстрого старта.
Шпаргалка: объекты событий¶
1 2 3 4 5 6 7 | |
Шпаргалка: цели переходов¶
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 | |
Чистые функции переходов¶
Начиная с XState версии 5.19.0
Вы можете вычислить следующее состояние и действия машины состояний без создания живого актора, используя чистые функции transition(machine, state, event) и initialTransition(machine). Эти функции возвращают кортежи [nextState, actions], которые представляют, что произойдёт во время перехода, но без выполнения каких-либо побочных эффектов.
Это полезно для:
- Серверных приложений и API-эндпоинтов
- Тестирования логики машины состояний
- Предварительного просмотра состояний и отладки
- Сценариев, где требуется детерминированное поведение
Совет
Для полной документации по чистым функциям переходов смотрите руководство Чистые функции переходов.
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 | |
Определение следующего состояния¶
Внимание
Рекомендуется использовать функции initialTransition(…) и transition(…) вместо getNextSnapshot(…) и getInitialSnapshot(…), которые будут объявлены устаревшими.
Когда вы создаёте актора машины состояний, следующее состояние определяется текущим состоянием машины и событием, отправленным актору. Если вы хотите определить следующее состояние вне актора, вы можете использовать функцию getNextSnapshot(…):
1 2 3 4 5 6 7 8 9 10 11 | |
Вы также можете определить начальное состояние машины с помощью функции getInitialSnapshot(…):
1 2 3 4 5 6 7 8 9 10 11 | |