Переходы¶
Переходы (Transitions) определяют, как конечный автомат реагирует на события.
API¶
Переходы состояний определяются на узлах состояний в свойстве on:
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 | |
В приведенном выше примере, когда автомат находится в состоянии pending и получает событие RESOLVE, он переходит в состояние resolved.
Переход между состояниями можно определить как:
- строка, например
RESOLVE: 'resolved', что эквивалентно ... - объект со свойством
target, например,RESOLVE: {target: 'resolved'}, - массив объектов перехода, которые используются для условных переходов
Метод автомата .transition()¶
Метод machine.transition(...) — это чистая функция, которая принимает два аргумента:
Метов возвращает новый экземпляр State, который является результатом выполнения всех переходов, разрешенных текущим состоянием и событием.
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Выбор разрешенных переходов¶
Разрешенный переход (enabled transition) — это переход, который будет выполняться условно, в зависимости от текущего состояния и события. Он будет принят тогда и только тогда, когда:
- он определяется на узле состояния, который соответствует текущему значению состояния
- защитник перехода (свойство
cond) вернулtrue - он не заменяется более специфичным переходом.
В иерархических автоматах переходы имеют приоритет в зависимости от того, насколько глубоко они находятся в дереве; более глубокие переходы более конкретны и, следовательно, имеют более высокий приоритет. Это работает аналогично тому, как работают события DOM: если вы нажмете кнопку, обработчик события щелчка непосредственно на кнопке будет более конкретным, чем обработчик события щелчка в окне.
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 | |
Дескрипторы событий¶
Дескриптор события (Event Descriptors) — это строка, описывающая тип события, которому будет соответствовать переход. Часто это эквивалентно свойству event.type объекта event, отправленного на конечный автомат:
1 2 3 4 5 6 7 8 9 10 11 12 | |
Другие дескрипторы событий включают:
- Дескрипторы нулевых событий
""(Null event descriptors), которые не соответствуют никаким событиям (т. е. "нулевые" события) и представляют собой переходы, выполненные сразу после входа в состояние. - Дескрипторы событий по-умолчанию
"*"(Wildcard event descriptors) (для версии 4.7+), которые срабатывают, если никакие другие события не подошли.
Переходы без смены состояния¶
Переходы без смены состояния (Self Transitions) — это когда состояние переходит в само себя, из которого оно может выйти, а затем снова войти в себя. Они могут быть внутренними или внешними:
- Внутренний переход (internal transition) не будет ни выходом, ни повторным входом, но может входить в другие дочерние состояния.
- Внешний переход (external transition) выйдет и повторно войдет в себя, а также может выйти или войти в дочерние состояния.
По умолчанию все переходы с указанной целью являются внешними.
См. действия при переходах без смены состояния для детальной информации, как это происходит.
Внутренние переходы¶
Внутренний переход (internal transition) — это переход, который не выходит из своего узла состояния. Внутренние переходы создаются путем указания относительной цели (например, '.left') или путем явной установки {internal: true} перехода. Например, рассмотрим автомат, который устанавливает абзац текста для выравнивания 'left', 'right', 'center' или 'justify':
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
Вышеупомянутый автомат запустится в состоянии left и в зависимости от того, что будет нажато, внутренне перейдет в другие дочерние состояния. Кроме того, поскольку переходы являются внутренними, вход, выход или какие-либо действия, определенные в родительском узле состояния, заново не выполняются.
Переходы, у которых есть {target: undefined} (или нет target), также являются внутренними переходами:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
Шпаргалка по внутренним переходам:
EVENT: '.foo'— внутренний переход к дочернему состояниюEVENT: { target: '.foo' }— внутренний переход к дочернему состоянию (начинается с'.')EVENT: undefined— запрещенный переходEVENT: { actions: [ ... ] }— внутренний переход без смены состоянияEVENT: { actions: [ ... ], internal: true }— внутренний переход без смены состояния, идентичен предыдущемуEVENT: { target: undefined, actions: [ ... ] }— внутренний переход без смены состояния, идентичен предыдущему
Внешние переходы¶
Внешние переходы (external transition) будут выходить и повторно входить в узел состояния, в котором определен переход. В приведенном выше примере для родительского узла состояния word (корневого узла состояния) при переходах выполняются действия выхода и входа.
По умолчанию переходы являются внешними, но любой переход можно сделать явно внешним, установив для перехода {internal: false}.
1 2 3 4 5 6 7 8 9 10 11 | |
Каждый переход, описанный выше, является внешним, и для него будут выполняться действия выхода и входа родительского состояния.
Шпаргалка по внешним переходам:
EVENT: { target: 'foo' }— все переходы в соседние узлы состояния — внешниеEVENT: { target: '#someTarget' }— все переходы к другим узлам состояния — внешниеEVENT: { target: 'same.foo' }— внешний переход к собственному дочернему узлу состояния (эквивалентно{ target: '.foo', internal: false })EVENT: { target: '.foo', internal: false }— внешний переход к дочернему узлу состояния — в противном случае это был бы внутренний переходEVENT: { actions: [ ... ], internal: false }— внешний переход без смены состоянияEVENT: { target: undefined, actions: [ ... ], internal: false }— внешний переход без смены состояния, аналогичен предыдущему
Проходные переходы¶
Warning
Синтаксис пустой строки ({on: {'': ...}}) не рекомендуется использовать c версии 5. Следует отдавать предпочтение новому синтаксису always c версии 4.11+. См. ниже раздел о переходах без событий, которые аналогичны проходным переходам.
Проходной переход (transient transition) — это переход, который активируется нулевым событием (null event). Другими словами, это переход, который выполняется немедленно (т. е. без инициирующего события), пока выполняются какие-либо условия:
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 | |
Как и переходы, проходные переходы могут быть указаны как один переход (например, '': 'someTarget') или как массив условных переходов. Если никакие условные переходы при проходном переходе не выполняются, автомат остается в том же состоянии.
Нулевые события всегда «отправляются» для каждого перехода, внутреннего или внешнего.
Безсобытийные "Always" переходы¶
Начиная с версии 4.11+
Бессобытийный переход (Eventless transition) — это переход, который всегда выполняется, когда автомат находится в состоянии, в котором он определен, и когда его cond защитной функцией оценивается как true. Они проверяются:
- сразу при входе в узел состояния
- каждый раз, когда машина получает действующее событие (независимо от того, запускает ли событие внутренний или внешний переход)
Бессобытийные переходы определены в свойстве always узла состояния:
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 | |
Бессобытийные переходы против переходов по-умолчанию¶
- Переходы по-умолчанию (Wildcard transitions) не проверяются при входе в узлы состояния, а бессобытийные переходы - проверяются. Защитные функции для переходов без событий выполняются перед тем, как делать что-либо еще (даже до выполнения защитных функций входных действий).
- Повторная оценка бессобытийных переходов запускается любым действующим событием. Повторная оценка переходов по-умолчанию запускается только событием, не совпадающим с явными дескрипторами событий.
Внимание
При неправильном использовании бессобытийных переходов можно создавать бесконечные циклы.
Бессобытийные переходы следует определять с помощью target, cond + target, cond + actions или cond + target + actions. Цель, если она объявлена, должна отличаться от узла текущего состояния. Бессобытийные переходы без target или cond вызовут бесконечный цикл. Переходы с cond и actions могут превратиться в бесконечный цикл, если его защитная функция cond продолжает возвращать true.
Подсказка
Когда проверяются бессобытийные переходы, их защитные функции повторно запускаются до тех пор, пока все они не вернут false, или переход с target не будет подтвержден. Каждый раз, когда какая-либо защитная функция возвращает true во время этого процесса, связанные с ним действия будут выполнены один раз. Таким образом, возможно, что во время одной микрозадачи некоторые переходы без целей выполняются несколько раз.
Это контрастирует с обычными переходами, где всегда можно сделать максимум один переход.
Запрещенные переходы¶
В XState «запрещенный» переход ("forbidden" transition) — это переход, который указывает, что переход состояния не должен происходить с указанным событием. То есть при запрещенном переходе ничего не должно происходить, и событие не должно обрабатываться узлами родительского состояния.
Запрещенный переход задается путем явного указания target как undefined. Это то же самое, что указать его как внутренний переход без действий:
1 2 3 4 5 6 7 8 | |
Например, мы можем смоделировать, что телеметрия может регистрироваться для всех событий, кроме случаев, когда пользователь вводит личную информацию:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | |
Подсказка
Обратите внимание, что при определении нескольких переходов с одним и тем же именем события в иерархической цепочке «предок-потомок» будет использоваться только самый внутренний переход. В приведенном выше примере именно поэтому действие logTelemetry, определенное в родительском событии LOG, не будет выполняться, как только компьютер достигнет состояния userInfoPage.
Несколько целей¶
Переход, основанный на одном событии, может иметь несколько целевых узлов состояния. Это необычно и допустимо только в том случае, если узлы состояния легальны; например, переход к двум узлам состояния одного и того же уровня в узле составного состояния является недопустимым, поскольку (непараллельный) конечный автомат может находиться только в одном состоянии в любой момент времени.
Несколько целей указываются в виде массива в target: [...], где каждая цель в массиве является относительным ключом или идентификатором узла состояния, как и отдельные цели.
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 | |
События по-умолчанию¶
Начиная с версии 4.7+
Переход, указанный с помощью дескриптора события по-умолчанию «*» (wildcard event descriptor), активируется любым событием. Это означает, что любое событие будет соответствовать переходу, который имеет: {"*": ...}, и если защитные функции вернут true, этот переход будет выполнен.
Явные дескрипторы событий всегда будут выбираться вместо дескрипторов событий по-умолчанию, если переходы не определены в массиве. В этом случае порядок переходов в массиве и определяет, какой из них будет выбран.
1 2 3 4 5 6 7 8 9 10 11 | |
Подсказка
Дескрипторы по-умолчанию (Wildcard descriptors) не ведут себя так же, как проходные переходы (transient transitions) (с нулевыми (null) дескрипторами событий). В то время как проходные переходы будут выполняться немедленно, когда состояние активно, переходы по-умолчанию (wildcard transitions) по-прежнему нуждаются в каком-либо событии, которое должно быть отправлено в его состояние для запуска.
Пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | |
Вопросы и ответы¶
Как мне выполнить логику if / else при переходах?¶
Иногда вам захочется сказать:
- Если что-то
true, перейти в это состояние - Если что-то еще
true, перейдите в это состояние - Иначе перейти в это состояние
Для этого можно использовать защищенные переходы.
Как мне перейти в любое состояние?¶
Вы можете перейти в любое состояние, присвоив этому состоянию собственный идентификатор и используя target: '#customId'. Вы можете прочитать полную документацию по пользовательским идентификаторам здесь.
Это позволяет вам переходить от дочерних состояний к одноуровневым родительским состояниям, например, в событиях CANCEL и done в этом примере: