Сравнение 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 Toolkit | XState | lite-fsm |
|---|---|---|---|
| Парадигма | Flux-архитектура | Statecharts/FSM | FSM + композиция автоматов |
| Размер (минимизированный + gzip) | @reduxjs/toolkit + react-redux: ≈19.2 KB | xstate + @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, middleware | Statecharts, actors, services, guards | States, events, reducer, effects |
| Масштабируемость | Подходит для больших приложений через слайсы | Подходит для сложных бизнес-процессов и statecharts | Подходит для компонентов и доменных автоматов через композицию |
| Иерархические состояния | Нет встроенной FSM-семантики | Да, встроенная поддержка | Композиционный паттерн, не встроенная иерархия одного автомата |
| Параллельные состояния | Нет встроенной FSM-семантики | Да, встроенная поддержка | Независимые автоматы в MachineManager |
| Визуализация | Redux DevTools | XState Inspector | Alpha 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-объект.
Для строготипизированных проектов это означает:
- Не нужна генерация дополнительных файлов.
- Можно один раз типизировать локальные фабрики и хуки приложения.
- Состояния выводятся из конфигурации автомата.
- Ошибки в target-ах и payload-ах ловятся на уровне TypeScript.
Когда использовать lite-fsm
lite-fsm обычно подходит в следующих случаях:
- Нужна компактная реализация конечных автоматов без полной statecharts-модели
- Важен размер runtime-части и контроль над подключаемыми пакетами
- Есть компоненты или доменные процессы с чёткими состояниями
- Команда уже знакома с reducer-подходом и хочет явно описывать переходы
- Нужна строгая типизация TypeScript без отдельного codegen-этапа
- Нужна часть statecharts-паттернов, но через композицию нескольких автоматов
- Для приложений вне React, где бизнес-логика инкапсулирована в автоматах, побочные эффекты находятся в effects, а сервисный слой передается через зависимости
- Когда нужен прямой доступ к состоянию всех автоматов, зарегистрированных в одном
MachineManager
Масштабирование и архитектура приложений
При разработке крупных приложений важно заранее определить, как доменные автоматы обмениваются событиями и читают состояние друг друга. В lite-fsm эту роль обычно выполняет MachineManager: он хранит состояние зарегистрированных автоматов и даёт единый transition.
Сравнение подходов к построению крупных приложений
| Аспект | Redux/Redux Toolkit | XState | lite-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.
- Создание автоматов по доменам: Создайте независимые автоматы для разных частей приложения
- Регистрация в MachineManager: Зарегистрируйте автоматы в MachineManager
- Прямое использование: Получайте доступ к состоянию любого автомата и отправляйте события между ними
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 и контролем над тем, как автоматы взаимодействуют друг с другом.