← Назад к вопросам

Для каких кейсов используется middleware

2.0 Middle🔥 161 комментариев
#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Для каких кейсов используется middleware

Middleware — это мощный паттерн в Redux и других системах управления состоянием. Это функции, которые перехватывают actions перед тем, как они попадут в reducer. Расскажу про практические кейсы.

1. Основная идея middleware

Action Dispatched
      |
      v
Middleware 1 (перехватывает, может изменить или отклонить)
      |
      v
Middleware 2 (дальше процесс)
      |
      v
Middleware 3
      |
      v
Reducer (обновляет state)
      |
      v
Subscribers (слушатели уведомляются)
const store = createStore(reducer, applyMiddleware(middleware1, middleware2));

2. Логирование (Logging)

Кейс: отслеживать все actions и изменения state

const loggerMiddleware = store => next => action => {
  console.log('Dispatching:', action);
  console.log('Previous state:', store.getState());
  
  const result = next(action); // Передаём action дальше
  
  console.log('New state:', store.getState());
  return result;
};

// Использование
const store = createStore(appReducer, applyMiddleware(loggerMiddleware));

Вывод:

Dispatching: { type: 'INCREMENT', payload: 1 }
Previous state: { count: 0 }
New state: { count: 1 }

Когда использовать:

  • Отладка приложения
  • Мониторинг production (отправка логов на сервер)
  • Аналитика пользовательских действий

3. Обработка асинхронных операций (Thunks)

Кейс: API запросы, асинхронные операции

const thunkMiddleware = store => next => action => {
  // Если action — это функция (thunk)
  if (typeof action === 'function') {
    // Вызываем её с dispatch и getState
    return action(store.dispatch, store.getState);
  }
  
  // Иначе передаём дальше
  return next(action);
};

// Action creator (возвращает функцию вместо объекта)
function fetchUser(id) {
  return async (dispatch, getState) => {
    dispatch({ type: 'SET_LOADING', payload: true });
    
    try {
      const response = await fetch(`/api/users/${id}`);
      const user = await response.json();
      dispatch({ type: 'SET_USER', payload: user });
    } catch (error) {
      dispatch({ type: 'SET_ERROR', payload: error.message });
    } finally {
      dispatch({ type: 'SET_LOADING', payload: false });
    }
  };
}

// Использование
store.dispatch(fetchUser(123));

Когда использовать:

  • API запросы
  • Асинхронные операции
  • Сложные потоки данных

Библиотеки:

  • redux-thunk (встроена)
  • redux-saga (более мощная)

4. Обработка ошибок (Error Handling)

Кейс: ловить и обрабатывать ошибки

const errorHandlingMiddleware = store => next => action => {
  try {
    return next(action);
  } catch (error) {
    console.error('Action failed:', action, error);
    
    // Отправляем error action
    store.dispatch({
      type: 'ERROR_OCCURRED',
      payload: error.message
    });
    
    throw error; // Пробрасываем дальше если нужно
  }
};

Когда использовать:

  • Глобальная обработка ошибок
  • Логирование ошибок
  • Показ пользователю уведомлений об ошибках

5. Аналитика (Analytics)

Кейс: отслеживать пользовательские действия

const analyticsMiddleware = store => next => action => {
  // Отправляем событие в Amplitude/Mixpanel/Google Analytics
  if (action.type.startsWith('USER_')) {
    analytics.track(action.type, {
      timestamp: Date.now(),
      state: store.getState()
    });
  }
  
  return next(action);
};

// Action creators
function trackUserLogin(userId) {
  return {
    type: 'USER_LOGGED_IN',
    payload: userId
  };
}

function trackUserLogout() {
  return {
    type: 'USER_LOGGED_OUT'
  };
}

Когда использовать:

  • Отслеживание пользовательских действий
  • Аналитика conversion
  • Отправка событий на Amplitude/Segment/Mixpanel

6. Валидация (Validation)

Кейс: валидировать actions перед их обработкой

const validationMiddleware = store => next => action => {
  const schema = actionSchemas[action.type];
  
  if (schema) {
    // Валидируем action используя Yup/Zod
    try {
      schema.validateSync(action.payload);
    } catch (error) {
      console.warn('Invalid action:', action, error);
      return; // Отклоняем invalid action
    }
  }
  
  return next(action);
};

// Schemas
const actionSchemas = {
  'SET_USER': Yup.object({
    id: Yup.number().required(),
    name: Yup.string().required(),
    email: Yup.string().email().required()
  }),
  'SET_AGE': Yup.object({
    age: Yup.number().min(0).max(150).required()
  })
};

Когда использовать:

  • Валидация данных
  • Превентация invalid state
  • Гарантия консистентности

7. Дебаунсирование и Throttling

Кейс: ограничить частоту обновлений

const debounceMiddleware = store => next => action => {
  const debounceMap = {};
  const DEBOUNCE_DELAY = 500;
  
  if (action.debounce) {
    // Очищаем старый таймер если существует
    if (debounceMap[action.type]) {
      clearTimeout(debounceMap[action.type]);
    }
    
    // Устанавливаем новый таймер
    debounceMap[action.type] = setTimeout(() => {
      next(action);
    }, DEBOUNCE_DELAY);
    
    return;
  }
  
  return next(action);
};

// Action с флагом debounce
function updateSearchQuery(query) {
  return {
    type: 'UPDATE_SEARCH',
    payload: query,
    debounce: true // Не вызываем API при каждом нажатии
  };
}

Когда использовать:

  • Поиск в реальном времени
  • Сохранение черновика
  • Ограничение API запросов

8. Persisting State (сохранение в localStorage)

Кейс: сохранять state в localStorage автоматически

const persistMiddleware = store => next => action => {
  const result = next(action);
  
  // После каждого action сохраняем state
  const state = store.getState();
  localStorage.setItem('appState', JSON.stringify(state));
  
  return result;
};

// Инициализация из localStorage
const savedState = localStorage.getItem('appState');
const initialState = savedState ? JSON.parse(savedState) : defaultState;
const store = createStore(appReducer, initialState, applyMiddleware(persistMiddleware));

Когда использовать:

  • Сохранение пользовательских настроек
  • Сохранение черновиков
  • Восстановление state после перезагрузки

Лучшая библиотека: redux-persist

import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';

const persistConfig = {
  key: 'root',
  storage,
  whitelist: ['user', 'preferences'] // Сохраняем только эти части
};

const persistedReducer = persistReducer(persistConfig, appReducer);
const store = createStore(persistedReducer);
const persistor = persistStore(store);

9. Авторизация и Permission Checking

Кейс: проверять права перед действиями

const authMiddleware = store => next => action => {
  const state = store.getState();
  const user = state.user;
  
  // Проверяем, имеет ли пользователь права на это действие
  const requiredRole = actionRoles[action.type];
  
  if (requiredRole && !user.roles.includes(requiredRole)) {
    console.warn('User does not have permission for', action.type);
    return; // Отклоняем action
  }
  
  return next(action);
};

// Определяем требуемые роли
const actionRoles = {
  'DELETE_USER': 'admin',
  'EDIT_POST': 'moderator',
  'VIEW_ANALYTICS': 'analyst'
};

Когда использовать:

  • Проверка прав доступа
  • Защита sensitive действий
  • RBAC (Role-Based Access Control)

10. Time-Travel Debugging (redux-devtools)

Кейс: отлаживать состояние приложения

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
  appReducer,
  composeEnhancers(
    applyMiddleware(thunkMiddleware, loggerMiddleware)
  )
);

// Теперь можешь в DevTools:
// - Видеть все actions
// - Путешествовать по времени (rewind/replay)
// - Диспетчить actions вручную
// - Экспортировать/импортировать state

11. Практический пример: Redux Thunk + Error Handling

// Middleware
const errorMiddleware = store => next => action => {
  try {
    return next(action);
  } catch (error) {
    store.dispatch({ type: 'SET_ERROR', payload: error.message });
  }
};

const thunkMiddleware = store => next => action => {
  if (typeof action === 'function') {
    return action(store.dispatch, store.getState);
  }
  return next(action);
};

// Reducer
const initialState = { user: null, loading: false, error: null };

function userReducer(state = initialState, action) {
  switch (action.type) {
    case 'FETCH_USER_START':
      return { ...state, loading: true, error: null };
    case 'FETCH_USER_SUCCESS':
      return { ...state, user: action.payload, loading: false };
    case 'FETCH_USER_ERROR':
      return { ...state, error: action.payload, loading: false };
    default:
      return state;
  }
}

// Thunk
function fetchUser(id) {
  return async (dispatch) => {
    dispatch({ type: 'FETCH_USER_START' });
    try {
      const response = await fetch(`/api/users/${id}`);
      const user = await response.json();
      dispatch({ type: 'FETCH_USER_SUCCESS', payload: user });
    } catch (error) {
      dispatch({ type: 'FETCH_USER_ERROR', payload: error.message });
    }
  };
}

// Store
const store = createStore(
  userReducer,
  applyMiddleware(thunkMiddleware, errorMiddleware)
);

// Usage
store.dispatch(fetchUser(123));

12. Современные альтернативы

Вместо Redux middleware можно использовать:

  1. Zustand (простое)
const useStore = create((set) => ({
  user: null,
  fetchUser: async (id) => {
    const user = await fetch(`/api/users/${id}`).then(r => r.json());
    set({ user });
  }
}));
  1. Recoil (с atoms)
const userAtom = atom({ key: 'user', default: null });

const fetchUserEffect = selector({
  key: 'fetchUser',
  get: async ({ get }) => {
    const response = await fetch('/api/user');
    return response.json();
  }
});
  1. TanStack Query (для API данных)
const { data, isLoading, error } = useQuery({
  queryKey: ['user', id],
  queryFn: async () => fetch(`/api/users/${id}`).then(r => r.json())
});

Ключевой вывод

Middleware используется для:

  1. Логирование — отслеживание actions и state
  2. Асинхронные операции — API запросы (redux-thunk)
  3. Обработка ошибок — глобальная обработка исключений
  4. Аналитика — отслеживание пользовательских действий
  5. Валидация — проверка корректности actions
  6. Оптимизация — дебаунсирование, кэширование
  7. Персистентность — сохранение в localStorage
  8. Авторизация — проверка прав доступа
  9. Отладка — Redux DevTools
  10. Трансформация — изменение actions перед reducer

Правило: Middleware должен быть чистой функцией без side effects (кроме логирования, аналитики, API запросов которые планируются).