Для каких кейсов используется middleware
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Для каких кейсов используется 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 можно использовать:
- Zustand (простое)
const useStore = create((set) => ({
user: null,
fetchUser: async (id) => {
const user = await fetch(`/api/users/${id}`).then(r => r.json());
set({ user });
}
}));
- 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();
}
});
- TanStack Query (для API данных)
const { data, isLoading, error } = useQuery({
queryKey: ['user', id],
queryFn: async () => fetch(`/api/users/${id}`).then(r => r.json())
});
Ключевой вывод
Middleware используется для:
- Логирование — отслеживание actions и state
- Асинхронные операции — API запросы (redux-thunk)
- Обработка ошибок — глобальная обработка исключений
- Аналитика — отслеживание пользовательских действий
- Валидация — проверка корректности actions
- Оптимизация — дебаунсирование, кэширование
- Персистентность — сохранение в localStorage
- Авторизация — проверка прав доступа
- Отладка — Redux DevTools
- Трансформация — изменение actions перед reducer
Правило: Middleware должен быть чистой функцией без side effects (кроме логирования, аналитики, API запросов которые планируются).