Skip to Content
👋 Добро пожаловать в документацию lite-fsm!

Сравнение lite-fsm с Redux/Redux Toolkit и XState

В этом разделе lite-fsm сравнивается с Redux/Redux Toolkit и XState. Цель сравнения — показать границы инструмента: где применима компактная FSM-модель, а где нужен runtime для statecharts с иерархией, actors и invoked services или классическая библиотека управления состоянием.

Примеры reducer-ов lite-fsm на этой странице написаны в стиле Immer: они мутируют draft state и предполагают, что MachineManager создан с immerMiddleware. Без middleware reducer должен вернуть новый объект { state, context }.

Общий обзор библиотек

Redux/Redux Toolkit

Redux  — одна из самых популярных библиотек для управления состоянием в React-приложениях.
Redux Toolkit  — официальная надстройка над Redux, которая упрощает работу с Redux и предоставляет утилиты для сокращения шаблонного кода.

Основные концепции Redux:

  • Единое централизованное хранилище состояния (Store)
  • Состояние изменяется только с помощью чистых функций (Reducers)
  • Действия (Actions) описывают намерение изменить состояние
  • Middleware для обработки побочных эффектов

XState

XState  — библиотека для создания, интерпретации и выполнения конечных автоматов и statecharts. Она предоставляет широкий набор механизмов для сложных автоматов: вложенные состояния, parallel states, guards, actions и invoked services.

Основные концепции XState:

  • Машины состояний и statecharts
  • Вложенные (иерархические) состояния
  • Параллельные состояния
  • История состояний
  • Действия на входе/выходе (entry/exit actions)
  • Охранные условия (guards)
  • Вызов сервисов и работа с обещаниями

lite-fsm

lite-fsm — легковесная TypeScript-библиотека для работы с конечными автоматами. Её фокус — компактный runtime, явные переходы, reducer-ы, effects и композиция нескольких автоматов через MachineManager.

Основные концепции lite-fsm:

  • Декларативное описание состояний и переходов
  • Состояния с контекстными данными
  • Эффекты, привязанные к состояниям
  • Middleware для расширения функциональности
  • Поддержка части statecharts-паттернов через композицию автоматов
  • Универсальное ядро, не зависящее от UI-фреймворка

Сравнительная таблица

ХарактеристикаRedux/Redux ToolkitXStatelite-fsm
ПарадигмаFlux-архитектураStatecharts/FSMFSM + композиция автоматов
Размер (минимизированный + gzip)@reduxjs/toolkit + react-redux: ≈19.2 KBxstate + @xstate/react: ≈17.2 KB@lite-fsm/core + @lite-fsm/react: ≈10.1 KB
Концепция состоянийСостояние как объект данныхЯвно определенные состояния со сложной иерархиейЯвно определенные состояния
Типизация (TypeScript)Типы store, actions и selectorsТипы machines, events, actors и snapshotsСтрогая типизация config, reducer и effects
Набор концепцийStore, actions, reducers, middlewareStatecharts, actors, services, guardsStates, events, reducer, effects
МасштабируемостьПодходит для больших приложений через слайсыПодходит для сложных бизнес-процессов и statechartsПодходит для компонентов и доменных автоматов через композицию
Иерархические состоянияНет встроенной FSM-семантикиДа, встроенная поддержкаКомпозиционный паттерн, не встроенная иерархия одного автомата
Параллельные состоянияНет встроенной FSM-семантикиДа, встроенная поддержкаНезависимые автоматы в MachineManager
ВизуализацияRedux DevToolsXState InspectorAlpha Visualizer на базе @lite-fsm/graph и DevTools middleware
ЭкосистемаБольшая экосистема ReduxБольшая экосистема XStateСобственные пакеты react, middleware, persist, graph, cli
Поддержка побочных эффектовЧерез middleware (redux-thunk, redux-saga)Встроенная (actions, invoked services)Через effects и middleware
Условные переходы (guards)Нет (только через reducers)Встроенная поддержкаЧерез reducer
СамопереходыНет (требует ручной реализации)Встроенная поддержкаЧерез null-переходы

Размеры в строке таблицы сравнивают React-связки без самого React: react, react-dom и react/jsx-runtime считались внешними зависимостями. Для Redux Toolkit используется @reduxjs/toolkit + react-redux, потому что официальная документация Redux  рекомендует React-приложениям использовать React-Redux bindings. Для XState используется xstate + @xstate/react, потому что документация XState React  указывает xstate как peer dependency React-пакета.

Значения Redux Toolkit и XState проверены через BundleJS /esbuild по public exports: @reduxjs/toolkit@2.12.0 + react-redux@9.3.0 — ≈19.2 KB, xstate@5.31.1 + @xstate/react@6.1.0 — ≈17.2 KB. Значение lite-fsm рассчитано тем же подходом по локальному dist: @lite-fsm/core + @lite-fsm/react — ≈10.1 KB.

При tree-shaking конкретных импортов фактический bundle может быть меньше. Локальная проверка типовых React-импортов дала средние значения ≈11.5 KB для Redux Toolkit + React-Redux, ≈13.9 KB для XState + React и ≈9.9 KB для lite-fsm + React.

Ключевые различия

Философия и дизайн

  • Redux/Redux Toolkit: Основывается на принципах Flux-архитектуры с централизованным хранилищем состояния. Фокусируется на предсказуемости и отладке через односторонний поток данных.
  • XState: runtime для statecharts со встроенными механизмами для сложного поведения.
  • lite-fsm: компактная FSM-модель с явным runtime, reducer-ами и effects. Подходит для базовых FSM-сценариев и части statecharts-паттернов через композицию автоматов, но не заменяет XState там, где нужна встроенная семантика statecharts.

Реализация Statecharts

XState реализует statecharts-семантику внутри одной машины. lite-fsm оставляет машину плоской, а похожие архитектурные задачи предлагает решать композиционно:

  • Иерархические состояния реализуются через композицию автоматов и реакцию на общие события
  • Параллельные состояния реализуются через независимые автоматы в MachineManager
  • Условные переходы реализуются через reducer
  • Самопереходы реализуются с помощью null-переходов

Это сохраняет небольшой API и простую модель исполнения. Цена такого подхода — часть возможностей statecharts остаётся паттерном проектирования, а не встроенной семантикой одного автомата.

Код и синтаксис: Сравнение реализации автомата формы обратной связи

Ниже приведены примеры реализации одного и того же автомата формы обратной связи в разных библиотеках:

Redux/Redux Toolkit

// Redux Toolkit пример import { createSlice, configureStore } from "@reduxjs/toolkit"; const feedbackSlice = createSlice({ name: "feedback", initialState: { state: "prompt", feedback: "", }, reducers: { feedbackGood: (state) => { state.state = "thanks"; }, feedbackBad: (state) => { state.state = "form"; }, updateFeedback: (state, action) => { state.feedback = action.payload.value; }, submitFeedback: (state) => { if (state.feedback.length > 0) { state.state = "thanks"; } }, goBack: (state) => { state.state = "prompt"; }, close: (state) => { state.state = "closed"; }, restart: (state) => { state.state = "prompt"; state.feedback = ""; }, }, }); const store = configureStore({ reducer: feedbackSlice.reducer, }); export const { feedbackGood, feedbackBad, updateFeedback, submitFeedback, goBack, close, restart } = feedbackSlice.actions;

XState

export const feedbackMachine = setup({ types: { context: {} as { feedback: string }, events: {} as Events }, guards: { feedbackValid: ({ context }) => context.feedback.length > 0 } }).createMachine({ id: 'feedback', initial: 'prompt', context: { feedback: '' }, states: { prompt: { on: { 'feedback.good': 'thanks', 'feedback.bad': 'form' } }, form: { on: { 'feedback.update': { actions: assign({ feedback: ({ event }) => event.value }) }, back: { target: 'prompt' }, submit: { guard: 'feedbackValid', target: 'thanks' } } }, thanks: {}, closed: { on: { restart: { target: 'prompt', actions: assign({ feedback: '' }) } } } }, on: { close: '.closed' } });

lite-fsm

const feedback = createMachine({ config: { "*": { CLOSE: "CLOSED", }, PROMPT: { FEEDBACK_GOOD: "THANKS", FEEDBACK_BAD: "FORM", }, THANKS: {}, FORM: { FEEDBACK_UPDATE: null, BACK: "PROMPT", SUBMIT: "THANKS", }, CLOSED: { RESTART: "PROMPT", }, }, initialState: "PROMPT", initialContext: { feedback: "", }, reducer: (state, action, { nextState }) => { if (action.type === "SUBMIT" && state.context.feedback.length === 0) { return; } state.state = nextState; switch (action.type) { case "RESTART": state.context.feedback = ""; break; case "FEEDBACK_UPDATE": state.context.feedback = action.payload.value; break; } }, });

Выводы по сравнению синтаксиса

Рассматривая одинаковый пример во всех трех библиотеках, можно сделать следующие наблюдения:

  • Redux Toolkit: хорошо описывает изменения данных, но состояния процесса и переходы между ними обычно остаются соглашением внутри reducer-ов.

  • XState: даёт выразительную модель statecharts, включая guards, actions, вложенность, parallel states и invoked services. Цена этой выразительности — больше концепций и более подробная структура машины.

  • lite-fsm: Использует плоскую конфигурацию переходов и reducer для обновления контекста. Разделение между config, reducer и effects делает поток исполнения явным, но сложные statecharts-возможности нужно моделировать через композицию нескольких автоматов.

Плоская структура конфигурации lite-fsm будет знакома разработчикам, которые уже используют reducer-подход, но хотят явно описывать состояния и переходы.

Сравнение систем типизации

Подходы к типизации различаются тем, где находится основная сложность:

  • XState: предоставляет типизацию через собственные API и helper-ы библиотеки. В сложных машинах типы часто описываются рядом с machine config и могут требовать явного сужения событий в отдельных ветках.

  • lite-fsm: опирается на TypeScript generics и вывод типов. Типы вроде TypedCreateMachineFn, TypedCreateReducerFn и TypedCreateConfigFn позволяют создать типизированные версии API без внешней генерации. Состояния выводятся из config, FSMEvent описывает события и payload, а reducer/effects получают типизированный action-объект.

Для строготипизированных проектов это означает:

  1. Не нужна генерация дополнительных файлов.
  2. Можно один раз типизировать локальные фабрики и хуки приложения.
  3. Состояния выводятся из конфигурации автомата.
  4. Ошибки в target-ах и payload-ах ловятся на уровне TypeScript.

Когда использовать lite-fsm

lite-fsm обычно подходит в следующих случаях:

  1. Нужна компактная реализация конечных автоматов без полной statecharts-модели
  2. Важен размер runtime-части и контроль над подключаемыми пакетами
  3. Есть компоненты или доменные процессы с чёткими состояниями
  4. Команда уже знакома с reducer-подходом и хочет явно описывать переходы
  5. Нужна строгая типизация TypeScript без отдельного codegen-этапа
  6. Нужна часть statecharts-паттернов, но через композицию нескольких автоматов
  7. Для приложений вне React, где бизнес-логика инкапсулирована в автоматах, побочные эффекты находятся в effects, а сервисный слой передается через зависимости
  8. Когда нужен прямой доступ к состоянию всех автоматов, зарегистрированных в одном MachineManager

Масштабирование и архитектура приложений

При разработке крупных приложений важно заранее определить, как доменные автоматы обмениваются событиями и читают состояние друг друга. В lite-fsm эту роль обычно выполняет MachineManager: он хранит состояние зарегистрированных автоматов и даёт единый transition.

Сравнение подходов к построению крупных приложений

АспектRedux/Redux ToolkitXStatelite-fsm
Глобальная модельЕдиное хранилищеЧасто дробится на actors/машины по доменамНесколько машин в одном или нескольких MachineManager
Организация автоматовСлайсы в едином стореИерархические отношения родитель-потомокПлоская структура через MachineManager
Связь между автоматамиЧерез dispatch в общий storeЧерез actor-связи, события и выбранную архитектуру приложенияЧерез общее событие, dependencies и состояние MachineManager
Доступ к состояниюЧерез store/selectorsЧерез snapshots/selectors/actor references в выбранной интеграцииЧерез manager.getState() для зарегистрированных машин
МасштабированиеЧерез slices/store conventionsЧерез actors, hierarchy и архитектуру приложенияЧерез несколько машин в MachineManager

Проблемы разработки крупных приложений и их решение

Архитектурные вопросы в крупных приложениях:

  • Коммуникация между независимыми процессами: нужно явно выбрать, кто отправляет события и где хранится состояние.
  • Доступ к состоянию соседних доменов: важно не превратить чтение общего состояния в скрытую связанность.
  • Сервисные границы: API, аналитика, storage и другие зависимости подключайте через явный слой dependencies или middleware.
  • Жизненный цикл процессов: долгоживущие и временные процессы моделируйте разными машинами или actor templates.

Как lite-fsm подходит к этим задачам:

  • MachineManager предоставляет точку доступа к зарегистрированным автоматам и их состояниям.
  • Одно событие проходит через зарегистрированные автоматы, поэтому простая координация не требует отдельного event bus.
  • Effects могут читать зависимости, включая getState, если вы передали их через setDependencies.
  • Единый API менеджера держит dispatch, подписки, middleware и snapshots в одном месте.

Разработка крупного приложения с lite-fsm

С lite-fsm можно начать с набора независимых доменных автоматов и регистрировать их в одном manager-е. Архитектурные границы всё равно стоит проектировать явно: какие автоматы отвечают за домен, какие события считаются публичными и какие зависимости доступны effects.

  1. Создание автоматов по доменам: Создайте независимые автоматы для разных частей приложения
  2. Регистрация в MachineManager: Зарегистрируйте автоматы в MachineManager
  3. Прямое использование: Получайте доступ к состоянию любого автомата и отправляйте события между ними
const checkout = createMachine({ config: { IDLE: { CHECKOUT: "PROCESSING", }, PROCESSING: { PAYMENT_RESOLVE: "SUCCESS", PAYMENT_REJECT: "ERROR", }, SUCCESS: {}, ERROR: { RETRY: "IDLE", }, }, initialState: "IDLE", initialContext: {}, effects: { PROCESSING: async ({ transition, paymentService, getState }) => { try { // Читаем данные из других автоматов const { items, total } = getState().cart.context; const { address, paymentMethods } = getState().user.context; const payment = await paymentService.processPayment({ items, total, address, paymentMethod: paymentMethods[0], }); transition({ type: "PAYMENT_RESOLVE", payload: { orderId: payment.orderId }, }); } catch (error) { transition({ type: "PAYMENT_REJECT", payload: { errorCode: error.code }, }); } }, }, }); const manager = MachineManager({ user, cart, products, checkout, }); manager.setDependencies({ paymentService, getState: manager.getState, }); const cartItems = manager.getState().cart.context.items; manager.transition({ type: "ADD_ITEM", payload: { productId: 123 } }); manager.transition({ type: "CHECKOUT" });

В таком сценарии MachineManager закрывает базовую координацию: эффекты могут читать состояние других автоматов через зависимость getState, а переходы отправляются обычными событиями.

Заключение

lite-fsm занимает место между классическими библиотеками управления состоянием и runtime для statecharts с иерархией, параллельными состояниями и invoked services. Его основной фокус — компактный API, строгая типизация и координация нескольких автоматов через MachineManager.

Хотя библиотека имеет интеграцию с React, её универсальное ядро подходит для разных JavaScript/TypeScript окружений: серверных, мобильных, десктопных и систем без пользовательского интерфейса. В таких сценариях lite-fsm остаётся инструментом для описания доменной логики с чётко определёнными состояниями, а не только способом подключить state к UI.

Этот подход рассчитан на проекты, где важен баланс между функциональностью, компактностью API и контролем над тем, как автоматы взаимодействуют друг с другом.

Last updated on