State Management
Statecharts: иерархия и параллелизм
Плоский автомат плеера быстро упирается в стену. Есть playing и paused, но к каждому добавляется muted или нет, fullscreen или нет. Четыре независимых режима дают плоский список из дюжины состояний с дублирующимися переходами. Statecharts Харела задают вопрос: а что, если playing и paused вложить внутрь состояния ready, а звук и полноэкранность вынести в параллельные регионы, которые живут одновременно и независимо, и тогда число явных состояний резко падает.
- Медиаплееры: вложенные playing и paused внутри ready плюс параллельные звук и режим экрана
- Редакторы: режим инструмента и состояние выделения как параллельные регионы
- Формы: общий режим отправки и независимые состояния валидации полей рядом
- Авторизация с подсостояниями: authenticating вкладывает проверку логина и двухфакторной
- Сложные виджеты, где entry и exit включают и выключают таймеры, подписки, анимации
Предварительные знания
- createMachine, состояния, события и переходы из урока про основы XState
- context и его изменение через assign
- Идея, что плоский набор состояний растёт комбинаторно при добавлении независимых режимов
Откуда взялись 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 — Иерархия и параллелизм продолжают борьбу с взрывом состояний, начатую в уроке про зачем