State Management

Statecharts: иерархия и параллелизм

Плоский автомат плеера быстро упирается в стену. Есть playing и paused, но к каждому добавляется muted или нет, fullscreen или нет. Четыре независимых режима дают плоский список из дюжины состояний с дублирующимися переходами. Statecharts Харела задают вопрос: а что, если playing и paused вложить внутрь состояния ready, а звук и полноэкранность вынести в параллельные регионы, которые живут одновременно и независимо, и тогда число явных состояний резко падает.

  • Медиаплееры: вложенные playing и paused внутри ready плюс параллельные звук и режим экрана
  • Редакторы: режим инструмента и состояние выделения как параллельные регионы
  • Формы: общий режим отправки и независимые состояния валидации полей рядом
  • Авторизация с подсостояниями: authenticating вкладывает проверку логина и двухфакторной
  • Сложные виджеты, где entry и exit включают и выключают таймеры, подписки, анимации

Предварительные знания

  • createMachine, состояния, события и переходы из урока про основы XState
  • context и его изменение через assign
  • Идея, что плоский набор состояний растёт комбинаторно при добавлении независимых режимов
  • XState: основы

Откуда взялись statecharts

Statecharts придумал израильский учёный Дэвид Харел в 1987 году в работе Statecharts: A Visual Formalism for Complex Systems. Он расширил классический конечный автомат тремя идеями: иерархией, где состояние содержит вложенные подсостояния, параллелизмом, где несколько регионов активны одновременно, и широковещательными событиями между ними. Изначально это создавалось для авионики и встроенных систем, где плоский автомат разрастался до неуправляемого числа состояний. Харел показал, что иерархия и параллелизм сворачивают этот взрыв, и спустя десятилетия XState принёс statecharts в веб почти в том же виде.

Иерархия: вложенные состояния

Первое расширение это иерархия. Состояние может содержать собственный словарь states с подсостояниями и своё initial. Тогда нахождение в родительском состоянии означает нахождение и в одном из его детей. Переход, описанный у родителя, применяется ко всем его подсостояниям сразу, поэтому общую логику не нужно дублировать в каждом ребёнке.

Здесь playing и paused вложены в ready. Переход PLAY и PAUSE живёт внутри, а событие STOP описано у родителя ready и переводит в paused из любого вложенного состояния. Без иерархии STOP пришлось бы дублировать в каждом подсостоянии. Иерархия собирает общий переход в одном месте и убирает повтор.

Состояние с подсостояниями называют составным. Нахождение в нём всегда означает нахождение в одном из его детей: нельзя быть в ready, не будучи в playing или paused. Это часть гарантии: иерархия не добавляет неопределённости, а структурирует уже существующие состояния.

Что даёт вложение состояний playing и paused внутрь составного состояния ready?

Параллелизм: одновременные регионы

Второе расширение это параллельные состояния. Состояние с типом parallel содержит несколько регионов, и система находится во всех них одновременно. Каждый регион это независимый автомат со своими подсостояниями. Это прямой ответ на взрыв комбинаций: вместо перемножения режимов в один плоский список они живут параллельно и не мешают друг другу.

Регионы sound и screen активны одновременно. Звук может быть muted или unmuted независимо от того, оконный режим или полноэкранный. Без параллелизма пришлось бы завести плоские состояния muted-windowed, muted-fullscreen, unmuted-windowed, unmuted-fullscreen и так далее, перемножая режимы. Параллельные регионы оставляют два маленьких автомата вместо одного большого с дублирующими переходами.

  • Плоский автомат — Каждый независимый режим перемножается с остальными. Два режима по два значения это уже четыре состояния, три это восемь
  • Параллельные регионы — Каждый режим это отдельный регион. Два региона по два значения это два маленьких автомата, а не четыре общих состояния

Признак, что куски стоит вынести в параллельные регионы, это их независимость: значения одного режима не зависят от значений другого. Звук и полноэкранность независимы, поэтому они регионы. А вот playing и paused взаимоисключающи, поэтому они вложенные подсостояния, а не параллельные регионы.

Когда два куска состояния стоит сделать параллельными регионами, а не вкладывать одно в другое?

Guards, actions и обработчики entry и exit

Statecharts добавляют к переходам ещё два механизма. Guard это условие на переходе: переход срабатывает, только если предикат-функция вернёт истину. Это позволяет иметь два перехода по одному событию с разными условиями: например, SUBMIT ведёт в success, если форма валидна, и обратно к ошибке, если нет.

По событию SUBMIT XState проверяет переходы по порядку. Первый сработает, только если guard вернёт истину, то есть форма валидна. Иначе сработает второй переход без guard и уведёт в invalid. Так одно событие даёт разный результат в зависимости от данных context, и логика выбора остаётся внутри схемы машины.

Второй механизм это действия на жизненном цикле состояния. actions на переходе выполняются в момент самого перехода. entry выполняется при входе в состояние, exit при выходе из него. Это удобно для управления ресурсами: при входе в loading запустить таймер таймаута в entry, при выходе остановить его в exit. Ресурс привязан к состоянию и не утекает, потому что выход из состояния всегда вызывает exit.

МеханизмКогда срабатываетЗачем
guardПеред переходом по событиюРазрешить переход только при истинном условии
actionsВ момент переходаПобочный эффект или изменение context через assign
entryПри входе в состояниеЗапустить таймер, подписку, анимацию
exitПри выходе из состоянияОстановить таймер, отписаться, очистить ресурс

Вместе иерархия, параллелизм, guards и обработчики entry и exit и есть statecharts Харела. Они берут конечный автомат из прошлых уроков и сворачивают взрыв состояний: вложенность убирает дублирование переходов, параллелизм убирает перемножение режимов, а guards и entry с exit держат условия и ресурсы внутри схемы, а не разбросанными по компонентам.

Чем guard отличается от обработчиков entry и exit в statechart?

Связь с другими темами

Этот урок завершает группу машин состояний. Он опирается на соседей:

  • XState: основы — Statecharts это надстройка над createMachine: те же состояния и переходы, но вложенные и параллельные
  • Машины состояний: зачем — Иерархия и параллелизм продолжают борьбу со взрывом числа состояний из вводного урока

Итог

  • Statecharts расширяют конечный автомат иерархией, параллелизмом и общими событиями (формализм Харела)
  • Иерархия: состояние содержит вложенные подсостояния, и переход родителя применяется ко всем детям
  • Параллелизм: несколько регионов активны одновременно и независимо друг от друга
  • guard это условие на переходе: переход срабатывает, только если предикат истинен
  • actions выполняются при переходе, а entry и exit при входе в состояние и выходе из него
  • Иерархия и параллелизм сворачивают комбинаторный взрыв плоского набора состояний

Связанные уроки

  • sm-26-xstate-basics — Statecharts надстраиваются над createMachine из урока про основы XState
  • sm-25-state-machines-intro — Иерархия и параллелизм продолжают борьбу с взрывом состояний, начатую в уроке про зачем
Statecharts: иерархия и параллелизм

0

1

Войти