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

Руководство по использованию

В этом разделе показано, как описать и запустить конечные автоматы с lite-fsm.

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

Библиотека построена вокруг нескольких ключевых концепций.

Состояния и переходы

Автомат всегда находится в одном из явно описанных состояний. Переход в другое состояние происходит при обработке события (action).

Контекст

Контекст — это объект с данными автомата. Его можно обновлять при каждом переходе в reducer-е.

Эффекты

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

Менеджер автоматов

MachineManager объединяет несколько автоматов, маршрутизирует события и запускает эффекты.

Создание автомата

Автомат описывается через createMachine:

import { createMachine } from "@lite-fsm/core"; const toggle = createMachine({ config: { INACTIVE: { TOGGLE: "ACTIVE", }, ACTIVE: { TOGGLE: "INACTIVE", }, }, initialState: "INACTIVE", initialContext: { lastToggledAt: null as number | null, }, reducer: (state, action, { nextState }) => { state.state = nextState; if (action.type === "TOGGLE") { state.context.lastToggledAt = action.payload.now; } }, effects: { ACTIVE: () => console.log("Активировано"), INACTIVE: () => console.log("Деактивировано"), }, });

Reducer-ы в примерах этого раздела написаны в стиле Immer — они мутируют draft и ничего не возвращают. Чтобы это работало, нужно подключить immerMiddleware (см. ниже). Без него reducer должен возвращать новый объект { state, context }.

Имена состояний и событий рекомендуем писать в UPPER_SNAKE_CASE (IDLE, LOAD_DATA, RESOLVE). Это делает граф переходов читаемым и хорошо отличает event types от обычных значений.

Запуск через MachineManager

import { MachineManager } from "@lite-fsm/core"; import { immerMiddleware } from "@lite-fsm/middleware/immer"; const manager = MachineManager({ toggle }, { middleware: [immerMiddleware] }); manager.onTransition((prev, next, action) => { console.log("Состояние изменилось:", { prev, next, action }); }); manager.transition({ type: "TOGGLE", payload: { now: Date.now() } }); const { state, context } = manager.getState().toggle;

Передайте в manager несколько автоматов, если процессы должны работать рядом. Каждый action проходит через все зарегистрированные машины; обработают его только те, у которых есть подходящий переход:

const manager = MachineManager({ toggle, counter, profile });

Внедрение зависимостей

Зависимости (сервисы, утилиты, доступ к manager-у) задаются через setDependencies. Они становятся доступны как поля параметра эффекта.

manager.setDependencies({ api: { fetchData: () => fetch("/api/data").then((r) => r.json()), }, getState: manager.getState, }); const data = createMachine({ config: { IDLE: { FETCH: "LOADING", }, LOADING: { FETCH_RESOLVE: "READY", FETCH_REJECT: "ERROR", }, READY: { RESET: "IDLE", }, ERROR: { RETRY: "LOADING", RESET: "IDLE", }, }, initialState: "IDLE", initialContext: { items: [] as string[], error: null as string | null, }, reducer: (state, action, { nextState }) => { state.state = nextState; if (action.type === "FETCH_RESOLVE") { state.context.items = action.payload.items; state.context.error = null; } if (action.type === "FETCH_REJECT") { state.context.error = action.payload.error; } }, effects: { LOADING: async ({ api, transition }) => { try { const items = await api.fetchData(); transition({ type: "FETCH_RESOLVE", payload: { items } }); } catch (error) { transition({ type: "FETCH_REJECT", payload: { error: error instanceof Error ? error.message : "unknown" }, }); } }, }, });

Внутри эффекта transition(...) отправляет новое событие через тот же manager.

Асинхронные эффекты

Эффект может быть async. Любой await внутри отрабатывает в обычном режиме:

effects: { LOADING: async ({ api, transition }) => { try { const result = await api.fetchSomething(); transition({ type: "FETCH_RESOLVE", payload: { data: result } }); } catch (error) { transition({ type: "FETCH_REJECT", payload: { error: error instanceof Error ? error.message : "unknown" }, }); } }, }

Если нужно отменять промежуточные transition при смене состояния, оборачивайте эффект в createEffect({ type: "latest", ... }) или используйте собственный cancelFn. Подробнее — в разделе Отмена эффектов.

Обработка событий в любом состоянии

Чтобы переход срабатывал из любого состояния, опишите его в специальном ключе "*". Явный переход в конкретном состоянии всегда приоритетнее wildcard:

const machine = createMachine({ config: { STATE_A: { EVENT_1: "STATE_B", }, STATE_B: { EVENT_2: "STATE_C", }, STATE_C: { EVENT_3: "STATE_A", }, "*": { RESET: "STATE_A", LOG: null, }, }, initialState: "STATE_A", initialContext: {}, });

Цель null — это self-transition: state не меняется, но action всё равно доходит до reducer-а. State-specific effects при этом не запускаются; для реакции на такие события используйте wildcard-effect.

Архитектура приложения на основе lite-fsm

Следующая sequence-диаграмма показывает базовый поток данных в приложении с lite-fsm:

Основные компоненты архитектуры:

  1. Пользовательский интерфейс (UI): Отображает состояние приложения и отправляет события в MachineManager.

  2. MachineManager: Центральный координатор, который:

    • Хранит общее состояние всех автоматов
    • Передает события в зарегистрированные автоматы
    • Запускает эффекты при изменении состояний
    • Уведомляет подписчиков об изменениях состояния
    • Внедряет зависимости (сервисы, утилиты) в эффекты
  3. Автоматы: Определяют:

    • Состояния и переходы между ними
    • Логику обновления контекста (данных)
    • Эффекты, выполняемые при входе в определенные состояния
  4. Сервисный слой: Содержит внешние зависимости:

    • API-клиенты для взаимодействия с серверами
    • Сервисы для работы с хранилищем, логированием и т.д.
    • Утилиты и хелперы для обработки данных

Такая архитектура разделяет UI, модель состояния и внешние сервисы. Это упрощает тестирование и делает точки взаимодействия явными.

Преимущества выделения бизнес-логики в автоматы

Практичный способ использовать lite-fsm — держать поведение процесса в автоматах (config, reducer, effects) и сервисном слое. Компоненты пользовательского интерфейса при этом отображают данные и отправляют события.

1. Разделение ответственности

Традиционный подход с React Hooks:

function UserProfile() { const [user, setUser] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); useEffect(() => { async function fetchUser() { setLoading(true); try { const response = await fetch("/api/user"); const data = await response.json(); setUser(data); } catch (err) { setError(err.message); } finally { setLoading(false); } } fetchUser(); }, []); // JSX с обработкой разных состояний... }

Подход с lite-fsm:

function UserProfile() { const { state, context } = useSelector((s) => s.user); const transition = useTransition(); useEffect(() => { transition({ type: "FETCH_USER" }); }, [transition]); // JSX с обработкой разных состояний... }

Бизнес-логика теперь живёт в автомате user: его состояние определяет UI, а эффекты выполняют все побочные действия.

2. Ключевые преимущества

Предсказуемость и отслеживаемость

  • Явные состояния: основные состояния процесса описаны в config
  • Единая точка входа: переходы проходят через transition
  • Логирование и отладка: изменения состояния отслеживаются через middleware или onTransition
  • Тестирование: reducer и effects проверяются отдельно от React-компонентов
  • Аналитика: явные события проще отправлять во внешние системы

Управление сложностью

  • Локализация изменений: изменения в процессе чаще остаются внутри одной машины
  • Разделение UI и поведения: компонентам не нужно знать детали async-сценариев
  • Контроль побочных эффектов: асинхронные операции сгруппированы в effects
  • Декомпозиция: независимые процессы выносятся в отдельные автоматы

Границы кода

  • Чистые компоненты: UI-компоненты фокусируются только на отображении и взаимодействии
  • Переиспользование логики: бизнес-логика не привязана к компонентам
  • Меньше привязки к UI: core-машины работают вне React
  • Снижение дублирования: общая логика управления состояниями вынесена на уровень автоматов

Поведение UI

  • Предсказуемые состояния UI: компонент читает state/context и показывает соответствующий экран
  • Меньше неучтённых комбинаций: явные состояния помогают увидеть пропущенные сценарии
  • Согласованное поведение: одинаковые события проходят через одну модель переходов

3. Сравнение с useEffect

Если сложный процесс реализован только через useEffect, часто появляются типичные проблемы:

  1. Разбросанность логики: Логика часто распределена между несколькими хуками в одном компоненте
  2. Нечёткие зависимости: Массив зависимостей часто становится источником ошибок и бесконечных циклов
  3. Сложность тестирования: Тестирование компонентов с несколькими useEffect требует сложной настройки
  4. Отсутствие явной модели состояний: Состояния неявно определяются комбинациями переменных состояния
  5. Сложность отмены эффектов: функция очистки в useEffect легко расходится с бизнес-состоянием компонента

В подходе с lite-fsm эти риски уменьшаются за счёт другой структуры:

  • Вся бизнес-логика централизована в автоматах
  • Все состояния явно определены
  • Переходы между состояниями контролируются автоматом
  • Эффекты привязаны к конкретным состояниям
  • Тестирование логики можно проводить без зависимости от React
Last updated on