Руководство по Redux для React

Быстрые ссылки
- Что делает Redux?
- Структура проекта
- Установка и настройка Redux
- Паттерны и лучшие практики
- Альтернативы и когда не стоит использовать Redux
Что делает Redux?
Проще говоря, Redux — это централизованное хранилище данных. Вся прикладная информация хранится в одном большом объекте состояния. Для визуализации удобно использовать Redux DevTools:

Ключевые идеи:
- Состояние иммутабельно. Чтобы изменить состояние, нужно отправить действие (action).
- Action описывает намерение изменить состояние (тип и полезная нагрузка — payload).
- Reducer берёт текущее состояние и action, и возвращает новое состояние.
- История действий сохраняется, что позволяет откатываться назад и отлаживать последовательности изменений (time-travel debugging).

Redux можно использовать с любым фронтенд-фреймворком, но чаще всего — с React. Под капотом React + Redux используют механизм контекста, поэтому для простых приложений Context API может быть достаточен. Однако DevTools, оптимизации и зрелая экосистема делают Redux удобным выбором для сложных проектов.
Важно: в TypeScript настройка типизации для Redux требует дополнительной дисциплины. Для удобства часто используют библиотеки, такие как typesafe-actions или Redux Toolkit, которые упрощают строго типизированные actions и reducers.
Ментальная модель Redux
Короткая дефиниция терминов:
- State: единый объект с текущими данными приложения.
- Action: объект с обязательным полем type, описывающий событие.
- Reducer: чистая функция (state, action) => newState.
- Store: контейнер, который хранит state и предоставляет методы dispatch и subscribe.
Представьте Redux как четкий контракт по изменению данных: все изменения — через actions, reducers предсказуемо возвращают новый state, а store — единая точка доступа.
Структурирование проекта
Правильная структура кода снижает временные и когнитивные издержки при масштабировании. Ниже — часто используемые паттерны и примеры.
Паттерн “по типам файлов”
Разделение по типу файлов (actions, reducers, middleware) выглядит так:
store/
actions/
reducers/
sagas/
middleware/
index.jsЭтот подход логичен, но в крупных проектах приводит к частым перекрёстным импортам: action и reducer для одной фичи оказываются в разных папках.
Паттерн “по фичам” (рекомендуется)
Лучше группировать код по функциональным областям (фичам). Тогда action, reducer, селекторы и тесты для одной сущности находятся рядом:
store/
features/
todos/
etc/
sagas/
middleware/
root-reducer.js
root-action.js
index.jsИмпорт упрощается:
import { todosActions, todosReducer } from 'store/features/todos'Можно держать Redux в отдельной папке (/store) или интегрировать в src/. Если компоненты привязаны к фичам, удобно хранить компоненты и reducers рядом.
Файлы типов в TypeScript
В каждом модуле фичи можно добавить types.ts для описания интерфейсов состояния и типов actions. Это упрощает рефакторинг и гарантирует согласованность.
Установка и базовая настройка
Установка через npm:
npm install redux react-reduxДля разработки полезен пакет redux-devtools (или Redux DevTools Extension):
npm install --save-dev redux-devtoolsСоздаём store. Сохраните файл как /store/index.js:
import { createStore } from 'redux'import rootReducer from ‘./root-reducer’
const store = createStore(rootReducer)
export default store;
Это минимальная конфигурация — позже вы подключите middleware, enhancers, роутер и т. п.
## Пример фичи: список задач (todos)
Для примера создадим простую фичу todo. Начнём с типов действий: создайте /features/todos/types.js
export const ADD = ‘ADD_TODO’
export const DELETE = 'DELETE_TODO'
export const EDIT = 'EDIT_TODO'
Затем actions: /store/features/todos/actions.js
import * as types from './types.js'export const addTodo = text => ({ type: types.ADD, text })
export const deleteTodo = id => ({ type: types.DELETE, id })
export const editTodo = (id, text) => ({ type: types.EDIT, id, text })
Reducer: /store/features/todos/reducer.js
import * as types from ‘./types.js’
const initialState = [
{
text: 'Hello World',
id: 0
}
]
export default function todos(state = initialState, action) {
switch (action.type) {
case types.ADD:
return [
...state,
{
id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1,
text: action.text
}
]
case types.DELETE:
return state.filter(todo =>
todo.id !== action.id
)
case types.EDIT:
return state.map(todo =>
todo.id === action.id ? { ...todo, text: action.text } : todo
)
default:
return state
}
}
Пояснения к reducer:
- ADD создаёт новый объект задачи и добавляет его в массив, вычисляя уникальный id.
- DELETE фильтрует задачи по id.
- EDIT возвращает новый массив, где изменённый элемент клонируется с обновлённым text.
Изначальное состояние (initialState) представляет собой только часть глобального состояния — branch state.todos.
Комбинирование редьюсеров
В /store/root-reducer.js импортируйте reducers и используйте combineReducers:
import { combineReducers } from 'redux';import todosReducer from ‘./features/todos/reducer’;
const rootReducer = combineReducers({
todos: todosReducer
})
export default rootReducer
Это гарантирует, что каждая фича занимает свою ветвь в глобальном state.
## Интеграция с React
Обёрните приложение в Provider, чтобы пробросить store во все компоненты:
import React from ‘react’;
import ReactDOM from 'react-dom';
// Redux Setup
import { Provider } from 'react-redux';
import store, { history } from './store';
ReactDOM.render(
, document.getElementById('root'));
Для взаимодействия с Redux в функциональных компонентах удобно использовать хуки: useDispatch и useSelector.
Пример контейнера добавления задач:
import React, { useState } from 'react';import ‘./Home.css’;
import { TodoList } from ‘components’
import { todosActions } from ‘store/features/todos’
import { useDispatch } from ‘react-redux’
function Home() {
const dispatch = useDispatch();
const [text, setText] = useState(“”);
function handleClick() {
dispatch(todosActions.addTodo(text));
setText(“”);
}
function handleChange(e: React.ChangeEvent
setText(e.target.value);
}
return (
);
}
export default Home;
Пример списка задач с useSelector:
import React from ‘react’;
import { useSelector } from 'store'
import { Container, List, ListItem, Title } from './styles'
function TodoList() {
const posts = useSelector(state => state.todos)
return (
{posts.map(({ id, title }) => (
{title} : {id}
))}
);
}
export default TodoList;
Замечание: в реальном коде стоит использовать ключи элементов, уникальные по id, а не по title.
Middleware, Sagas, Thunks и побочные эффекты
Redux по умолчанию синхронен. Для асинхронных задач используют middleware:
- redux-thunk — простой и популярный для небольших случаев (функции вместо actions);
- redux-saga — побочные эффекты выражаются в виде генераторов, удобен для сложных потоков и отмены задач;
- redux-observable — использует RxJS и хорошо подходит для реактивных потоков.
Выбор зависит от требований команды и сложности: если нужно много управления параллелизмом и отменой — saga; если несколько запросов — thunk.
Оптимизация производительности
Проблемы перерисовок часто связаны с неправильными селекторами или частыми изменениями ссылок на объекты.
Рекомендации:
- Нормализуйте состояние (как в базе данных), чтобы избежать глубоких копий и переприсваиваний.
- Используйте мемоизированные селекторы — библиотека reselect.
- Разбивайте state на независимые ветви и комбинируйте reducers.
- Избегайте избыточных dispatch в render-пайплайне.
Пример простой мемоизации с reselect:
import { createSelector } from 'reselect'
const getTodos = state => state.todos
export const getVisibleTodos = createSelector([getTodos, (state, filter) => filter], (todos, filter) => {
// вычисления, которые выполняются только при изменении зависимостей
return todos.filter(todo => /* фильтрация по filter */ true)
})Шаблоны проектирования и лучшие практики
- Duck-паттерн: храните actions, types и reducer в одном модуле для фичи.
- Action creators: используйте функции, которые формируют объект действия; это упрощает тестирование.
- Минимизируйте логику в компонентах — выносите её в селекторы, thunks/sagas или utils.
- Тестируйте reducers и селекторы отдельно — они чистые функции и легко тестируются.
Тестирование
Тестируйте три уровня:
- Unit tests для reducers и селекторов (чистые функции).
- Integration tests для thunks/sagas (можно мокать API).
- E2E тесты для пользовательских потоков.
Пример теста reducer (псевдокод):
const state = []
const action = { type: types.ADD, text: 'test' }
const nextState = todos(state, action)
expect(nextState.length).toBe(1)
expect(nextState[0].text).toBe('test')Приём добавления новой фичи в Redux — чеклист
Для разработчика:
- Создал директорию /store/features/
- Добавил types.js, actions.js, reducer.js
- Добавил селекторы в selectors.js
- Написал unit-тесты для reducer и селекторов
- Добавил интеграционные тесты для асинхронной логики (если есть)
- Зарегистрировал reducer в root-reducer
- Протестировал UI: добавление/удаление/редактирование
Для ревьюера/архитектора:
- Логическая изоляция фичи (минимальные зависимости)
- Понятные названия типов действий
- Отсутствие побочных эффектов в reducer
- Мемоизация селекторов при необходимости
- Документация и комментарии к нестандартной логике
Альтернативы Redux и когда не стоит его использовать
Когда Redux может быть избыточен:
- Небольшие приложения с локальным состоянием — Context API или локальные useState/useReducer достаточно.
- UI с минимальным обменом данных между компонентами.
Популярные альтернативы:
- Context API + useReducer — простая замена для небольших случаев.
- MobX — реактивная модель, проще в настройке для CRUD-приложений, но менее предсказуемая.
- Recoil — библиотека от Facebook с атомарным состоянием.
- Zustand — лёгкий, минималистичный менеджер состояния.
Когда выбирать Redux:
- Сложная логика синхронизации состояния между множеством компонентов.
- Требуется мощная отладка (DevTools, time-travel).
- Нужна строгая предсказуемость и тестируемость редьюсеров.
Примеры типичных проблем и как их решать
Перерисовки компонентов при неизменности данных
- Проверьте, не создаются ли новые объекты в селекторах.
- Используйте reselect или useMemo.
Дублирование типов действий
- Организуйте namespace для типов, например todos/ADD.
Смешивание побочных эффектов и редьюсеров
- Перенесите асинхронную логику в thunks/sagas; редьюсеры должны быть чистыми.
Сложности с типизацией в TypeScript
- Используйте Redux Toolkit или typesafe-actions; экспортируйте типы экшенов и состояния.
Примеры конфигураций middleware
Подключение redux-thunk:
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import rootReducer from './root-reducer'
const store = createStore(rootReducer, applyMiddleware(thunk))
export default storeПодключение Redux DevTools (безопасный pattern):
import { createStore, applyMiddleware, compose } from 'redux'
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(rootReducer, composeEnhancers(applyMiddleware(...middleware)))Пример перехода с Context API на Redux — простой план миграции
- Идентифицируйте глобальные данные и места использования.
- Создайте структуру features и временно пробросьте store через Provider.
- Постепенно перепишите потребителей: замените useContext -> useSelector, а колбэки на dispatch.
- Удалите устаревший Context после полной миграции.
Полезные паттерны разработки
- Normalization: храните сущности как словари по id, а не массивы больших объектов.
- Selectors: инкапсулируют логику выборки из state.
- Action creators как единственная точка формирования payload (удобно для логирования).
- Immutable updates: используйте spread, map, filter или библиотеки типа Immer для безопасных мутаций.
Маленькая шпаргалка команд и зависимостей
- Установка Redux и React-Redux: npm install redux react-redux
- Devtools: установить расширение браузера или пакет redux-devtools
- Middleware: redux-thunk, redux-saga, redux-logger
- Утилиты: reselect, normalizr, immer
Decision flowchart для выбора менеджера состояния
flowchart TD
A[Небольшое приложение?] -->|Да| B[Используйте useState/useReducer или Context API]
A -->|Нет| C[Нужно централизованное управление данных?]
C -->|Нет| B
C -->|Да| D[Есть сложные побочные эффекты?]
D -->|Да| E[Рассмотрите Redux + Saga]
D -->|Нет| F[Redux + Thunk или Zustand]
E --> G[Требуется time-travel/DevTools?]
G -->|Да| H[Redux 'DevTools']
G -->|Нет| FПримеры ролей: что должен делать разработчик, ревьюер и архитектор
Разработчик:
- Реализует фичу в папке feature
- Пишет тесты для reducer
- Проводит ручное тестирование UI
Ревьюер:
- Проверяет чистоту reducer
- Смотрит на названия action types
- Проверяет мемоизацию селекторов
Архитектор:
- Утверждает паттерн структурирования
- Контролирует подключение middleware
- Обеспечивает соответствие требованиям безопасности и масштабируемости
Критерии приёмки
- Все actions имеют явные типы и payload
- Reducer не выполняет побочных эффектов
- Нет лишних перерисовок компонентов
- Тесты для reducer и селекторов покрывают ключевые сценарии
Примеры приёмочных тестов
- Добавление задачи увеличивает длину массива
- Удаление задачи с несуществующим id не ломает состояние
- Редактирование задачи изменяет только поле text у нужного id
Частые ошибки и как их избежать
- Хранение больших UI-станов в Redux (формы, локальные поля) — держите их локально.
- Мутирование состояния в reducer — используйте чистые операции и не изменяйте входной state.
- Использование title как key в списках — используйте уникальный id.
Советы по отладке
- Подключите Redux DevTools и отслеживайте последовательность actions.
- Логируйте payload в action creators временно для отладки.
- Используйте hot-reload редьюсеров в development для быстрой итерации.
Security и приватность
Redux хранит данные в памяти клиента. Не храните в state секреты: токены доступа, пароли, PII — держите их в защищённом хранилище и минимально передавайте в client-side.
Краткая методология внедрения Redux в проект
- Пилотная фича: выберите одну среднюю по сложности фичу и реализуйте на Redux.
- Оценка: проверьте сложность, скорость разработки, удобство отладки.
- Итерация: доработайте структуру модулей и селекторов.
- Масштабирование: постепенно переносите другие фичи.
Глоссарий 1 строкой
- Store — контейнер состояния.
- Action — дескриптор события { type, payload }.
- Reducer — чистая функция, возвращающая новый state.
- Middleware — прослойка для перехвата/обработки dispatch.
- Selector — функция выбора части состояния.
Когда Redux не подходит
- Простая форма с локальным состоянием.
- Приложение с почти нулевым взаимодействием между компонентами.
Ресурсы для изучения
- Официальная документация Redux
- Redux Toolkit для упрощённой конфигурации
- Ресурсы по redux-saga и redux-thunk
FAQ
Какой минимальный набор файлов для простой фичи?
Минимально: types.js, actions.js, reducer.js и подключение reducer в root-reducer.
Нужно ли всегда использовать combineReducers?
Для модульности и предсказуемости структуры state рекомендуется, особенно для средних и больших проектов.
Как выбрать между redux-thunk и redux-saga?
Если нужны простые асинхронные вызовы — thunk. Если сложная координация асинхронных потоков и отмена задач — saga.
Краткое резюме
Redux даёт предсказуемую модель управления состоянием, которая особенно полезна в больших приложениях с множеством взаимодействий. Для небольших проектов его можно заменить более лёгкими инструментами. При внедрении важно придерживаться чистых редьюсеров, нормализации состояния и мемоизации селекторов.
Важно: начните с малого — пилотная фича позволит оценить ценность Redux для вашего проекта.
Похожие материалы
Троян Herodotus: как он работает и как защититься
Включить новое меню «Пуск» в Windows 11
Панель полей PivotTable в Excel — руководство
Включить новый Пуск в Windows 11 — инструкция
Как убрать дубликаты Диспетчера задач Windows 11