State Management
XState: основы
Идея конечного автомата ясна: набор состояний и разрешённые переходы. Но как выразить её в коде так, чтобы переходы соблюдались, а не оставались договорённостью в комментариях? Можно вручную писать switch по состоянию и событию, но он расползается и не отлавливает забытые случаи. XState задаёт вопрос: а что, если описать состояния, события и переходы как данные в createMachine, а библиотека сама гарантирует, что переход вне схемы не случится, и нарисует диаграмму по этому описанию.
- Загрузка данных как машина idle, loading, success, error с переходами по событиям
- Многошаговые формы и мастера: состояние шага и условия перехода в createMachine
- Светофоры UI и тогглы со сложной логикой: модалки, тулбары, режимы редактора
- Платежи и заказы, где набор статусов и переходов критичен и проверяется визуализатором
- Команды, рисующие машину в Stately-визуализаторе до написания кода компонента
Предварительные знания
- Идея конечного автомата: состояния, события и разрешённые переходы
- Понимание, почему невозможные состояния опасны и как их исключают
- Базовая работа с хуками React и подпиской компонента на изменения
Откуда взялся XState
XState создал Дэвид Куршид в 2017 году, чтобы принести statecharts Дэвида Харела в мир JavaScript. Библиотека описывает машину как объект-конфигурацию: состояния, события, переходы, контекст. К версии XState v5, вышедшей в 2024 году, модель сместилась к акторам: машина это актор, который получает события, держит состояние и может порождать другие акторы. Параллельно команда сделала визуальный редактор Stately, где машину можно нарисовать мышью и получить код, или наоборот построить диаграмму по существующей конфигурации. Так описание состояний и его картинка стали одним и тем же артефактом.
createMachine: состояния, события, переходы
Машина в XState описывается функцией createMachine как объект-конфигурация. В нём задают id, начальное состояние initial и словарь states. У каждого состояния есть блок on, где ключи это имена событий, а значения это целевые состояния. Это прямое выражение таблицы переходов: из какого состояния по какому событию в какое.
Эта конфигурация и есть схема из предыдущего урока, но теперь исполняемая. Из idle событие FETCH ведёт в loading. Из loading RESOLVE ведёт в success, REJECT в error. Событие, которого нет в блоке on текущего состояния, игнорируется: переход вне схемы просто не происходит. Гарантия разрешённых переходов теперь не в комментарии, а в данных машины.
Из этой же конфигурации редактор Stately строит диаграмму состояний автоматически. Описание машины и её визуальная схема это один артефакт: меняется конфигурация, меняется картинка. Это удобно для обсуждения логики с командой и для поиска недостающих переходов глазами.
Что описывает блок on у состояния в конфигурации createMachine?
context и расширенное состояние
Конечных состояний мало, чтобы хранить всё. Состояние loading не говорит, сколько было попыток, а success не несёт сами данные. Для таких расширенных данных у машины есть context: объект произвольных полей, который живёт рядом с конечным состоянием. Конечное состояние отвечает на вопрос где мы, а context на вопрос с какими данными.
context меняется не напрямую, а через action assign в описании перехода. По событию INC выполняется assign, который пересчитывает count из текущего контекста. Это держит изменения данных внутри схемы машины: данные меняет только переход, и только так, как описано. Здесь переход не меняет конечное состояние, а лишь обновляет context, что тоже допустимо.
- Конечное состояние — Один из дискретного набора: idle, loading, success. Отвечает на вопрос в каком режиме система
- context — Объект с данными: счётчики, ответы, ошибки. Отвечает на вопрос с какими значениями, меняется через assign
Деление на конечное состояние и context важно держать чистым. Дискретные режимы это конечные состояния, а количественные и переменные данные это context. Попытка завести отдельное конечное состояние под каждое значение счётчика вернёт тот самый взрыв состояний, ради устранения которого автомат и берут.
Зачем машине нужен context помимо конечных состояний?
useMachine: машина в React
Чтобы использовать машину в компоненте, в XState v5 есть хук useMachine из пакета @xstate/react. Он принимает машину и возвращает кортеж: текущий snapshot состояния и функцию send для отправки событий. Компонент читает state, чтобы понять текущее состояние и context, и вызывает send, чтобы инициировать переходы.
Текущее состояние проверяют методом state.matches, который возвращает признак нахождения в нужном состоянии. События шлют через send объектом с полем type. Важно, что send не присваивает состояние напрямую: он передаёт событие машине, а уже та решает, разрешён ли переход. Отправка события, для которого в текущем состоянии нет перехода, безопасно игнорируется.
В XState v5 машина под капотом это актор. useMachine создаёт экземпляр актора, привязанный к жизни компонента, подписывается на его snapshot и перерисовывает компонент при смене состояния или context. Через send компонент общается с актором событиями, а не дёргает поля напрямую. Это та же модель сообщений, что лежит в основе акторного подхода XState v5.
Так замыкается цепочка из группы: конечный набор состояний и переходы из предыдущего урока становятся исполняемой конфигурацией в createMachine, а useMachine подключает её к UI. Переход вне схемы не происходит ни в логике, ни в компоненте, и невозможные состояния остаются невыразимыми уже в рантайме.
Что возвращает хук useMachine и как компонент инициирует переход?
Связь с другими темами
Этот урок про реализацию автоматов в XState. Рядом стоят соседи:
- Машины состояний: зачем — XState это конкретная реализация идей конечного автомата из предыдущего урока
- Statecharts: иерархия и параллелизм — На createMachine дальше строятся вложенные и параллельные состояния
Итог
- createMachine описывает машину как конфигурацию: states, события, переходы и начальное состояние
- Состояние меняется только событием: в блоке on состояния заданы переходы по именам событий
- context хранит расширенные данные машины (счётчики, ответы), которые меняются через assign в actions
- В XState v5 машина это актор: он получает события через send и отдаёт текущее состояние
- useMachine из @xstate/react подключает машину к компоненту: отдаёт state и функцию send
- Переход вне схемы не происходит, и это та же гарантия невозможных состояний, но уже в рантайме
Связанные уроки
- sm-25-state-machines-intro — XState это реализация конечных автоматов, разобранных в предыдущем уроке
- sm-27-statecharts — На основе createMachine дальше строятся иерархические и параллельные statecharts