Redux & State Management
Плюсы и минусы Redux
Плюсы:
- Централизованное хранилище (single source of truth)
- Предсказуемые мутации — только через
dispatch(action)→reducer - DevTools с time-travel отладкой (можно «перематывать» состояние)
- Middleware (Redux Thunk, Redux Saga) для асинхронной логики
Минусы:
- Много бойлерплейта: actions, action creators, reducers, constants
- Избыточен для небольших приложений (хватит
useState+Context) - Обновления фреймворка могут ломать обратную совместимость
Redux Toolkit (RTK) сильно сокращает бойлерплейт, но всё равно требует понимания концепций.
Альтернативы: Zustand, Jotai, MobX, React Query (для серверного состояния)
Для чего был придуман Redux?
Redux решает проблему непредсказуемого глобального состояния в больших приложениях. Основная идея — единый источник правды, состояние которого меняется только через явные действия.
Проблема без Redux:
Компонент A → пропс → Компонент B → пропс → Компонент C
// Prop drilling: нужно пробрасывать через все уровни
// Трудно отследить, кто и когда изменил данные
Три принципа Redux:
- Единый источник правды — весь state в одном Store.
- State только для чтения — изменить можно только через dispatch(action).
- Чистые редьюсеры — изменение state = чистая функция без побочных эффектов.
Из чего состоит Redux?
UI → dispatch(action) → Middleware → Reducer → Store → UI
// 1. ACTION — описывает что произошло
const increment = (amount) => ({
type: 'counter/increment',
payload: amount,
});
// 2. REDUCER — чистая функция: (state, action) => newState
const counterReducer = (state = 0, action) => {
switch (action.type) {
case 'counter/increment': return state + action.payload;
case 'counter/decrement': return state - action.payload;
default: return state;
}
};
// 3. STORE — хранилище
import { createStore } from 'redux';
const store = createStore(counterReducer);
store.getState(); // 0
store.dispatch(increment(5)); // отправить действие
store.getState(); // 5
// 4. SELECTOR — функция для получения части state
const selectCount = (state) => state.counter;Redux Toolkit (современный подход):
import { createSlice, configureStore } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: 0,
reducers: {
increment: (state, action) => state + action.payload,
decrement: (state, action) => state - action.payload,
},
});
const store = configureStore({ reducer: { counter: counterSlice.reducer } });
export const { increment, decrement } = counterSlice.actions;Для чего используется Middleware?
Middleware — это функция между dispatch и reducer. Позволяет перехватывать actions и добавлять логику.
dispatch(action) → [middleware1] → [middleware2] → reducer
// Структура кастомного middleware
const myMiddleware = (store) => (next) => (action) => {
console.log('Before:', store.getState());
const result = next(action); // передать дальше
console.log('After:', store.getState());
return result;
};Основные применения:
// 1. Асинхронные действия — Redux Thunk
const fetchUser = (id) => async (dispatch) => {
dispatch({ type: 'user/loading' });
try {
const user = await api.getUser(id);
dispatch({ type: 'user/loaded', payload: user });
} catch (e) {
dispatch({ type: 'user/error', payload: e.message });
}
};
// В Redux Toolkit (createAsyncThunk)
const fetchUser = createAsyncThunk('user/fetch', async (id) => {
const res = await api.getUser(id);
return res.data;
});
// 2. Логирование — redux-logger
// 3. Обработка ошибок — централизованный try/catch
// 4. Аналитика — отслеживание actions| Middleware | Назначение |
|---|---|
redux-thunk | Асинхронные actions (функции) |
redux-saga | Сложная async-логика через генераторы |
redux-logger | Логирование actions в console |
redux-persist | Сохранение state в localStorage |
Context API vs Redux
| Параметр | Context API | Redux |
|---|---|---|
| Сложность | Минимальная | Высокая (экшены, редьюсеры) |
| Производительность | Ре-рендер всех потребителей контекста | Точечные подписки через селекторы |
| DevTools | Нет | Redux DevTools (time-travel) |
| Middleware / Async | Нет | Есть (thunk, saga) |
| Размер бандла | 0 КБ (встроен в React) | заметный: RTK + React-Redux ~10+ КБ (min+gzip) |
| Когда использовать | Тема, язык, авторизация — редко меняется | Сложный shared state, частые обновления |
Правило выбора:
Редко меняется (тема, auth) → Context API
Часто обновляется, сложная логика → Redux Toolkit
В большинстве проектов комбинируют оба: Context для темы/локали, Redux для данных приложения.
В чём разница между Redux и MobX?
| Redux | MobX | |
|---|---|---|
| Модель изменений | Явные actions → редьюсеры (чистые функции), предсказуемый поток | Реактивность (observable state), изменения часто через прямые присваивания / декораторы |
| Структура | Один store (или slice’ы), нормализованный подход | Обычно множество observable-объектов, ООП-стиль |
| Производительность | Тонкая подписка через селекторы (reselect, RTK) | Гранулярные реакции на изменение полей |
| Шаблонность | Больше бойлерплейта (без Toolkit); с RTK — меньше | Меньше шаблонного кода |
| Девтулзы / отладка | Redux DevTools, time-travel | MobX DevTools |
| Когда уместно | Командные соглашения, строгий однонаправленный поток, аудит действий | Быстрый старт, сложные графы зависимостей, «реактивный» UI |
Оба решают задачу управления состоянием; Redux акцентирует явный поток событий, MobX — автоматическое отслеживание зависимостей.
Что такое Redux Thunk и Redux Saga?
| Redux Thunk | Redux Saga | |
|---|---|---|
| Идея | dispatch может принимать функцию; thunk получает dispatch и getState | Побочные эффекты описываются генераторами и итераторами yield |
| Асинхронность | async/await внутри thunk | call, put, take, fork и др. — декларативные эффекты |
| Сложность | Проще войти | Круче кривая обучения |
| Отмена / гонки | Вручную (флаги, AbortController) | Встроенные паттерны (takeLatest, race, cancel) |
| Зависимости | redux-thunk (часто встроен в RTK) | redux-saga |
Thunk — middleware: если action — функция, middleware вызывает её и не передаёт функцию в редьюсер.
Saga — отдельные «процессы», слушающие actions и оркестрирующие асинхронные сценарии.
Как подключить Thunk / Saga к хранилищу?
Redux Thunk (классический createStore):
import { createStore, applyMiddleware } from 'redux';
import { thunk } from 'redux-thunk'; // v3+: именованный импорт (раньше был default)
import rootReducer from './reducers';
const store = createStore(rootReducer, applyMiddleware(thunk));Redux Toolkit (Thunk уже в configureStore по умолчанию):
import { configureStore } from '@reduxjs/toolkit';
const store = configureStore({
reducer: rootReducer,
// thunk включён по умолчанию; можно добавить свой middleware:
middleware: (getDefault) => getDefault().concat(myMiddleware),
});Redux Saga:
import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import rootReducer from './reducers';
import rootSaga from './sagas';
const saga = createSagaMiddleware();
const store = createStore(rootReducer, applyMiddleware(saga));
saga.run(rootSaga);С RTK saga подключают через configureStore + middleware: (gD) => gD({ thunk: false }).concat(saga) или оставляют thunk и добавляют saga рядом — зависит от проекта.
Для собеса: Thunk — функции в
dispatch; Saga —sagaMiddleware.run(rootSaga)после создания store.