Переходы¶
Переходы (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
в этом примере: