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

Middleware

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

Что такое middleware?

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

Доступ к модулю middleware

import { immerMiddleware, devToolsMiddleware } from "lite-fsm/middleware";

Встроенные middleware

immerMiddleware

Middleware для интеграции с библиотекой Immer , которая упрощает работу с иммутабельными данными.

Одно из ключевых преимуществ использования immerMiddleware - возможность мутировать состояние напрямую, без необходимости создавать новые объекты вручную. Более того, при использовании immerMiddleware в reducer функции можно не делать return вообще. Immer автоматически отследит все мутации и создаст новый иммутабельный объект состояния.

import { MachineManager, createMachine } from "lite-fsm"; import { immerMiddleware } from "lite-fsm/middleware"; // Создание автомата const todoMachine = createMachine({ config: { IDLE: { ADD_TODO: null, TOGGLE_TODO: null, REMOVE_TODO: null, }, }, initialState: "IDLE", initialContext: { todos: [], }, // С Immer можно мутировать черновик контекста reducer: (state, action, options) => { const { context } = state; // Используем стандартный reducer, Immer-middleware позаботится о создании нового объекта switch (action.type) { case "ADD_TODO": // Мутируем context напрямую (Immer создаст новую копию под капотом) context.todos.push({ id: action.payload.id, text: action.payload.text, completed: false, }); break; case "TOGGLE_TODO": const todo = context.todos.find((t) => t.id === action.payload.id); if (todo) { todo.completed = !todo.completed; } break; case "REMOVE_TODO": const index = context.todos.findIndex((t) => t.id === action.payload.id); if (index !== -1) { context.todos.splice(index, 1); } break; } // При использовании immerMiddleware можно не делать return вообще // Immer автоматически применит все сделанные мутации контекста // И вернет новое состояние с сохранением иммутабельности // Но можно также явно вернуть состояние: return { state: options.nextState, context, // Возвращаем "мутированный" контекст, Immer преобразует его в новый объект }; }, }); // Создание менеджера с middleware const manager = MachineManager({ todo: todoMachine }, { middleware: [immerMiddleware] });

Пример reducer без return

import { MachineManager, createMachine } from "lite-fsm"; import { immerMiddleware } from "lite-fsm/middleware"; // Создание автомата без возврата значения в reducer const counterMachine = createMachine({ config: { IDLE: { INCREMENT: null, DECREMENT: null, RESET: null, }, }, initialState: "IDLE", initialContext: { count: 0, }, // В этом reducer мы не делаем return вообще reducer: (state, action, options) => { const { context } = state; switch (action.type) { case "INCREMENT": // Просто мутируем контекст context.count += 1; break; case "DECREMENT": context.count -= 1; break; case "RESET": context.count = 0; break; } // Нет return - immerMiddleware автоматически создаст новое состояние // с учетом всех внесенных изменений }, }); // Обязательно используем immerMiddleware const manager = MachineManager({ counter: counterMachine }, { middleware: [immerMiddleware] });

devToolsMiddleware

Middleware для интеграции с Redux DevTools Extension , который позволяет отлаживать состояние приложения и переходы между состояниями.

Важно: Данная middleware находится в альфа-версии и в настоящее время поддерживает только базовое логирование событий. Остальные возможности API, которые предоставляет официальный пакет Redux DevTools (такие как time-travel debugging, экспорт/импорт состояний, и тестирование на основе записей), находятся в разработке и будут добавлены в будущих версиях.

DevTools middleware принимает только опцию blacklistActions - массив типов действий, которые не будут отправляться в DevTools. Redux DevTools Extension сам по себе поддерживает множество настроек через свой интерфейс.

import { MachineManager, createMachine } from "lite-fsm"; import { devToolsMiddleware } from "lite-fsm/middleware"; // Создание автомата const counterMachine = createMachine({ config: { IDLE: { INCREMENT: null, DECREMENT: null, RESET: null, }, }, initialState: "IDLE", initialContext: { count: 0, }, reducer: (state, action) => { const { state: currentState, context } = state; switch (action.type) { case "INCREMENT": context.count += 1; break; case "DECREMENT": context.count -= 1; break; case "RESET": context.count = 0; break; } }, }); // Настраиваем параметры DevTools const devToolsOptions = { blacklistActions: ["TIMER_TICK", "AUTO_SAVE"], // События, которые не нужно отслеживать в DevTools }; // Создание менеджера с middleware const manager = MachineManager({ counter: counterMachine }, { middleware: [devToolsMiddleware(devToolsOptions)] });

Создание собственного middleware

Вы можете создать собственное middleware для решения специфических задач:

import { MachineManager, createMachine } from "lite-fsm"; // Middleware для логирования всех переходов const loggingMiddleware = (manager) => (next) => (action) => { console.group(`%c Action: ${action.type}`, "color: #4CAF50"); console.log("Предыдущее состояние:", manager.getState()); console.log("Действие:", action); // Вызываем следующее middleware в цепочке const result = next(action); console.log("Новое состояние:", manager.getState()); console.groupEnd(); return result; }; // Middleware для аналитики const analyticsMiddleware = (manager) => (next) => (action) => { // Измеряем время выполнения действия const startTime = performance.now(); // Вызываем следующее middleware в цепочке const result = next(action); const endTime = performance.now(); const duration = endTime - startTime; // Отправляем данные в аналитику if (window.analytics) { window.analytics.track("State Transition", { action: action.type, duration, timestamp: new Date().toISOString(), }); } return result; }; // Middleware для сохранения состояния в localStorage const persistMiddleware = (storageKey = "lite-fsm-state") => (manager) => (next) => (action) => { // Вызываем следующее middleware в цепочке const result = next(action); // Сохраняем состояние после обработки действия try { const state = manager.getState(); localStorage.setItem(storageKey, JSON.stringify(state)); } catch (err) { console.error("Error persisting state:", err); } return result; }; // Создание автомата const appMachine = createMachine({ // ...конфигурация автомата }); // Создание менеджера с несколькими middleware const manager = MachineManager( { app: appMachine }, { middleware: [loggingMiddleware, analyticsMiddleware, persistMiddleware("my-app-state")], }, );

Порядок выполнения middleware

Middleware выполняются в порядке их определения в массиве. Каждое middleware может:

  1. Выполнить действия перед передачей действия следующему middleware
  2. Изменить или полностью заменить действие
  3. Предотвратить вызов следующего middleware, отменив действие
  4. Выполнить действия после обработки действия всеми следующими middleware
Action -> Middleware 1 -> Middleware 2 -> ... -> Reducer -> ... -> Middleware 2 -> Middleware 1 -> Result

Расширенные примеры использования middleware

Кеширование действий

// Middleware для кеширования результатов некоторых действий const cachingMiddleware = (options = {}) => { const cache = new Map(); const { ttl = 5000, actionTypes = [] } = options; return (manager) => (next) => (action) => { // Если действие не должно кешироваться if (!actionTypes.includes(action.type)) { return next(action); } // Создаем ключ кеша const cacheKey = JSON.stringify({ type: action.type, payload: action.payload }); // Проверяем кеш if (cache.has(cacheKey)) { const { result, timestamp } = cache.get(cacheKey); const now = Date.now(); // Если кеш все еще актуален if (now - timestamp < ttl) { console.log(`[Cache] Использование кешированного результата для ${action.type}`); return result; } } // Выполняем действие const result = next(action); // Сохраняем результат в кеше cache.set(cacheKey, { result, timestamp: Date.now() }); return result; }; }; // Использование const manager = MachineManager( { data: dataMachine }, { middleware: [ cachingMiddleware({ actionTypes: ["FETCH_DATA", "LOAD_PROFILE"], ttl: 10000, // 10 секунд }), ], }, );

Обработка ошибок

// Middleware для централизованной обработки ошибок const errorHandlingMiddleware = (errorHandler) => (manager) => (next) => (action) => { try { return next(action); } catch (error) { console.error(`Error processing action ${action.type}:`, error); // Вызываем пользовательский обработчик ошибок errorHandler(error, action, manager); // Возвращаем состояние без изменений return manager.getState(); } }; // Использование const manager = MachineManager( { app: appMachine }, { middleware: [ errorHandlingMiddleware((error, action, manager) => { // Отправляем ошибку в сервис мониторинга reportErrorToService(error, { action, state: manager.getState(), }); // Показываем уведомление пользователю showErrorNotification(`Произошла ошибка: ${error.message}`); }), ], }, );

Заключение

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

Last updated on