Redux: что это такое и зачем она нужна
Управляем состоянием приложения одной библиотекой.
Redux — это библиотека для управления состоянием данных и пользовательским интерфейсом в JavaScript-приложениях. Её используют, когда данных становится много и они меняются в разных компонентах ПО: это потенциально может привести к проблемам, например к использованию в функциях устаревших значений переменных.
Из этой статьи вы узнаете, как работать с Redux и из каких компонентов состоит библиотека. Также мы напишем простое приложение, отработав теорию на практике. Статья будет полезна в первую очередь начинающим фронтендерам, работающим с React.
Содержание
- Для чего нужна Redux
- Из чего состоит библиотека
- Как установить Redux и написать с ней приложение
- Что дальше
Для чего нужна Redux
Redux используют в приложениях с большим объёмом данных, которые часто обновляются и используются в разных частях интерфейса. В такой ситуации сложно поддерживать согласованность состояния программы вручную: отдельные функции могут обращаться к устаревшим значениям, а интерфейс — вести себя непредсказуемо.
Ключевой термин в абзаце выше, который важно знать, чтобы оценить преимущества Redux, — это состояние. Под ним понимают единый объект данных, который описывает текущее состояние всего приложения в конкретный момент времени. Можно сказать, что это «снимок» того, что сейчас знает о себе программа: значения переменных, активные элементы интерфейса, пользовательский ввод и так далее.
Redux решает проблему поддержания согласованности состояния приложения за счёт строгого набора правил. Библиотека однозначно указывает, кто может инициировать изменение данных, когда это допустимо и по каким правилам они обновляются. Ключевой принцип в том, что любой компонент в приложении не имеет права сам менять данные. Он может только запросить изменение, отправив об этом сообщение.
Такой подход основан на Flux-архитектуре. Чтобы разобраться в ней, сначала посмотрим на то, как происходит обмен данными в классической MVC-архитектуре.
В традиционном подходе компоненты могут сами менять данные, а связи между ними часто двусторонние. Это создаёт проблему, так как число взаимодействий с ростом сложности приложения растёт и в нём появляются неочевидные зависимости. В итоге изменение в одном компоненте меняет интерфейс, интерфейс влияет на другой компонент, тот — на третий и так далее. Всё это делает работу приложения с MVC-архитектурой непредсказуемой, усложняет отладку и масштабирование.

Инфографика: Майя Мальгина для Skillbox Media
Для решения этой проблемы появилась Flux-архитектура с предсказуемым однонаправленным потоком данных. В ней изменения всегда идут по цепочке — от компонента (Action) через посредника (Dispatcher), который обновляет хранилище состояния приложения (Store) и, как результат, интерфейс (View). В отличие от MVC-архитектуры, интерфейс не может изменить состояние напрямую.
Каждое хранилище отвечает за свою часть данных, а состояние приложения распределено между ними и обновляется по единым правилам.

Инфографика: Майя Мальгина для Skillbox Media
Redux — одна из практических реализаций Flux-архитектуры с важным отличием: на смену нескольким хранилищам (Store) приходит одно.
Но ради надёжности приходится жертвовать удобством — весь многоступенчатый процесс изменения состояния требуется прописывать для любых действий. Если для важных процедур вроде изменения баланса это оправдано, то для какой-нибудь мелочи (выпадающего окна или изменения цвета кнопки) — уже излишне.
Проблему такой «бюрократии» частично решает Redux Toolkit (RTK) — набор инструментов от создателей библиотеки. RTK автоматизирует рутинные части процесса и позволяет разработчикам обойтись меньшим объёмом кода. О нём мы подробно поговорим в практической части статьи.

Читайте также:
Из чего состоит Redux
Перейдём от концепции к реализации. Redux не вводит новые абстракции, а только задаёт строгие правила взаимодействия обычных сущностей JavaScript. В библиотеке есть шесть ключевых элементов: store, state, action, reducer, dispatch и subscribe.
В примерах кода в этом разделе мы будем использовать приложение интернет-провайдера с одним абонентом. Это поможет лучше понять компоненты библиотеки.
Store, dispatch и subscribe
Store — главный объект, который связывает все части Redux воедино. Он хранит состояние, предоставляет к нему доступ, позволяет его изменять и подписываться на изменения тем компонентам, которых эти изменения затрагивают.
В коде это JavaScript-объект, который создаётся функцией createStore() при запуске приложения и остаётся в единственном экземпляре во время его работы.
const store = createStore (reducer);
createStore() принимает в качестве аргумента reducer — функцию, которая содержит конкретные операции. Необязательным вторым параметром является изначальное состояние хранилища — массив значений для компонентов. Например, такой:
const store = createStore(reducer);У store есть два важных метода — dispatch и subscribe. dispatch() — это метод хранилища, который принимает в качестве аргумента действие и передаёт его редьюсеру.
store.dispatch(action)subscribe() — это метод, который передаёт компоненту новое состояние приложения. Без него фактические изменения не отображались бы в интерфейсе.
Метод принимает функцию-слушателя listener, которая будет автоматически запускаться каждый раз, когда кто-то вызывает dispatch:
store.subscribe(listener)State
State — это снимок состояния приложения в конкретный момент времени. Он хранит данные, необходимые для отслеживания текущего статуса, — число пользователей, суммарную стоимость товаров в корзине, баланс счёта и другие изменяющиеся со временем данные.
В коде это объект, который хранит массив значений для компонентов. В примере банковского приложения это выглядит так:
const state = {
user: { name: "Alex", id: 1 },
balance = 0,
isInternetActive: false
};State в Redux доступен только для чтения. Вы не сможете написать state.balance = 500. Единственный способ изменить его — создать новый стейт через цепочку действий.
Action
Action описывает событие и то, как требуется изменить состояние в связи с ним. В коде это простой объект с двумя свойствами — обязательным type и необязательным payload:
const action = { // Действие с двумя свойствами
type: "PAY_INTERNET", // Что случилось? Оплата интернета
payload: 500 // Данные: сумма списания
};
const action = { // Действие с одним свойством
type: "RECONNECTION" // Что случилось? Переподключение
};Reducer
Редьюсер определяет, как действие должно изменить состояние, и меняет его. В коде он представлен функцией, которая в качестве аргументов принимает текущее состояние и действие, а возвращает новое состояние. Так как редьюсер работает с разными действиями, то тело функции состоит из switch-case-конструкции.
Если редьюсер не знает, как обработать поступившее действие, то обязан вернуть текущее состояние как есть. Иначе приложение перестанет работать.
// Начальное состояние (баланс на момент открытия счёта)
const initialState = {
balance: 1000,
isInternetActive: false
};
const bankReducer = (state = initialState, action) => {
switch (action.type) {
case "PAY_INTERNET":
return {
...state, // Копируем все поля старого стейта
balance: state.balance - action.payload, // Обновляем баланс
isInternetActive: true // Включаем интернет
};
case "DEPOSIT_MONEY":
return {
...state,
balance: state.balance + action.payload
};
// Если тип экшена нам незнаком, возвращаем текущий стейт без изменений
default:
return state;
}
};Жизненный цикл одной операции
Соберём всё вместе. Как будет происходить оплата интернета в приложении, которое мы описали:
- Пользователь нажимает кнопку «Оплатить».
- Генератор действий создаёт объект: { type: «PAY_INTERNET», payload: 500 }.
- Компонент отправляет этот объект в хранилище: store.dispatch (action).
- Хранилище «просыпается» и передаёт текущий стейт и экшен редьюсеру.
- Функция-редьюсер считает: 1000 − 500 = 500. Она возвращает новый объект стейта, где баланс равен 500, а интернет включён.
- Хранилище сохраняет этот новый стейт вместо старого.
- Интерфейс, подписанный на хранилище, видит обновление и перерисовывает баланс на экране.
Как видите, Redux позволяет создать цепочку действий со строгими правилами, что позволяет отображать правильный баланс в приложении.
Устанавливаем Redux и пишем приложение
Теория — это хорошо, но Redux проще понять на практике. В этом разделе мы напишем часть простого банковского приложения на React. В ней реализуем весь цикл: от создания хранилища до отрисовки кнопок «Пополнить» и «Снять» в интерфейсе.

Читайте также:
Устанавливаем Redux
В прошлых разделах мы подробно разобрали чистую Redux. Её методы — это фундаментальные концепции, которые важно понимать. Однако в современной разработке почти всегда используется Redux Toolkit (RTK).
Redux Toolkit — это официальный рекомендуемый набор инструментов для эффективной работы с Redux. Он был создан, чтобы упростить работу с библиотекой.
Для старта работы с ним установите пакет @reduxjs/toolkit — он уже включает в себя ядро Redux, react-redux — библиотеку-связку, и Redux Toolkit. Для этого введите в терминале:
npm install @reduxjs/toolkit react-reduxПишем приложение
Разделим процесс на четыре шага, чтобы не запутаться: пропишем логику, подключим Redux, нарисуем интерфейс и протестируем результат. Разберём каждый из этапов подробно.
Шаг 1. Прописываем логику. Для лучшей организации кода в проекте принято выносить всю логику Redux в отдельную папку.
Создайте внутри папки src/ папку store/ с файлом index.js. Откройте его и вставьте код из блока ниже. Здесь мы используем функции Redux Toolkit configureStore() для создания хранилища и createSlice() для редьюсера.
// src/store/index.js
// Здесь мы создаём Redux Slice и Redux Store
import { configureStore, createSlice } from '@reduxjs/toolkit';
// 1. Создаём срез состояния (Slice) с помощью createSlice
// Эта функция позволяет определить начальное состояние и редьюсеры и автоматически генерирует action creators в одном месте
const bankSlice = createSlice({
name: 'bank', // Уникальное имя этого среза. Используется для Action Type, например: bank/deposit
initialState: {
balance: 0, // Начальное значение баланса
},
reducers: {
// В Redux Toolkit можно писать мутабельный код внутри редьюсеров
// Благодаря встроенной библиотеке Immer, RTK сам превратит его в иммутабельное обновление состояния под капотом, обеспечивая безопасность
deposit: (state, action) => {
state.balance += action.payload; // Выглядит как прямое изменение, но безопасно!
},
withdraw: (state, action) => {
state.balance -= action.payload; // Выглядит как прямое изменение, но безопасно!
},
// Здесь могли бы быть и другие редьюсеры, например toggleInternet: (state) => { state.isInternetActive = !state.isInternetActive; },
},
});
// Экспортируем наши функции-генераторы действий (action creators), которые были автоматически созданы createSlice
export const { deposit, withdraw } = bankSlice.actions;
// 2. Создаём Redux Store с помощью configureStore
// configureStore — это обёртка над createStore, которая автоматически настраивает Redux DevTools, добавляет middleware (например, thunk) и упрощает объединение редьюсеров
export const store = configureStore({
reducer: {
// Здесь мы определяем, какие редьюсеры отвечают за какие части общего состояния
// Ключ bank будет использоваться для доступа к состоянию этого среза: state.bank
bank: bankSlice.reducer,
},
});Сравните с чистой Redux из предыдущего раздела: кое-что изменилось, а именно — появились две новые сущности:
- createSlice(). Вместо ручного создания initialState, switch/case редьюсера и отдельных объектов action мы определяем всё это в одном месте. RTK сам создаст необходимые типы действий и функции-генераторы для них.
- configureStore(). Это упрощённая версия createStore, которая позволяет легко комбинировать несколько редьюсеров для упрощения работы.
Шаг 2. Подключаем Redux к React. Redux Store готов. Следующий шаг — сделать его доступным для всех React-компонентов нашего приложения.
Откройте src/index.js. Не перепутайте его с src/store/index.js, с которым мы работали в предыдущем шаге! Вставьте в src/index.js код:
// index.js (или main.jsx)
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux'; // Импортируем провайдер
import { store } from './store'; // Импортируем наш Store
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);Здесь используется <Provider> — специальный компонент Redux, который передаёт Redux Store во всё дерево вложенных компонентов. Благодаря этому любой компонент может получить доступ к состоянию и экшенам напрямую, без явной передачи state через пропсы от родителя к потомкам. Такой подход избавляет от prop drilling и упрощает работу с общим состоянием приложения по мере его роста.

Инфографика: Майя Мальгина для Skillbox Media
Шаг 3. Создаём компоненты интерфейса. Теперь создадим компонент, который будет отображать баланс и кнопки для взаимодействия с пользователем. Мы будем использовать хуки useSelector() для чтения состояния из Store и useDispatch() для отправки действий.
Откройте src/App.js и вставьте в него следующий код:
// src/App.jsx (или src/App.js)
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
// Импортируем action creators (deposit, withdraw) из нашего Redux Store
import { deposit, withdraw } from './store';
function App() {
// 1. Получаем текущий баланс из Redux Store с помощью хука useSelector
// state здесь — это весь объект состояния Store
// Мы обращаемся к 'state.bank.balance', потому что наш редьюсер был передан в configureStore под ключом bank
const balance = useSelector((state) => state.bank.balance);
// 2. Получаем функцию dispatch с помощью хука useDispatch
// Эта функция нужна для отправки (диспетчеризации) наших действий в Store
const dispatch = useDispatch();
return (
<div className="App" style={appContainerStyle}>
<h1>Банк Redux Toolkit</h1>
<h2>Ваш баланс: {balance}$</h2>
<div className="buttons">
<button
onClick={() => dispatch(deposit(100))} // Отправляем действие "deposit" с суммой 100
style={depositButtonStyle}
>
Пополнить на 100$
</button>
<button
onClick={() => dispatch(withdraw(50))} // Отправляем действие "withdraw" с суммой 50
style={withdrawButtonStyle}
>
Снять 50$
</button>
</div>
</div>
);
}
export default App;
const appContainerStyle = {
textAlign: 'center',
fontFamily: 'Arial, sans-serif',
padding: '20px',
maxWidth: '500px',
margin: '50px auto',
border: '1px solid #eee',
borderRadius: '8px',
boxShadow: '0 4px 8px rgba(0,0,0,0.1)',
backgroundColor: '#fff',
};
const baseButtonStyle = {
margin: '10px',
padding: '10px 20px',
fontSize: '16px',
cursor: 'pointer',
border: 'none',
borderRadius: '5px',
color: 'white',
fontWeight: 'bold',
};
const depositButtonStyle = {
...baseButtonStyle,
backgroundColor: '#4CAF50', // Зелёный для пополнения
};
const withdrawButtonStyle = {
...baseButtonStyle,
backgroundColor: '#f44336', // Красный для снятия
};Здесь мы тоже кое-что улучшили по сравнению с чистой Redux из прошлого раздела. Добавилось несколько сущностей:
- useSelector(). Теперь мы обращаемся к state.bank.balance, потому что наш редьюсер был передан в configureStore() под ключом bank. Если бы у нас было несколько срезов (например, bankSlice и userSlice), мы бы обращались к state.bank или state.user.
- Диспетчер с action creators. Вместо ручного создания объекта { type: «DEPOSIT», payload: 100 } мы просто вызываем функцию deposit(100), а RTK сам генерирует за нас правильный объект действия.
Шаг 4. Запускаем и проверяем приложение. Запустите проект, находясь в корневой папке терминала:
npm startТеперь откройте приложение по адресу http://localhost:3000/. Всё работает: кнопки позволяют менять баланс, и он отображается на экране.

Скриншот: Google Chrome / Skillbox Media
Приложение получилось небольшим, но демонстрирующим преимущества Redux:
- Баланс хранится в одном-единственном объекте. В программе нет локальных состояний с балансом в компонентах, которые могли бы рассинхронизироваться и привести к его неверному отображению.
- Ничто не может менять баланс напрямую — вместо этого вы отправляете действия, запускающие его обновление.
- Приложение строго следует циклу «Событие → Действие → Обработка → Обновление → Отображение», что делает приложение предсказуемым и простым для отладки.
Что дальше
Теперь вы освоили основы Redux. Чтобы их закрепить и узнать о других возможностях библиотеки, попробуйте написать несколько дополнительных модулей для приложения. Например, можно добавить функциональность регистрации и авторизации или отображение истории транзакций. Перед этим загляните на официальный сайт Redux Toolkit — там есть разделы с примерами и гайдами, которые мы рекомендуем изучить для углублённого понимания.
А ещё RTK с React могут быть полезны в бэкенд-разработке: в Redux Toolkit есть инструмент для управления состоянием загрузки, ошибками и кешированием — RTK Query.
Больше интересного про код — в нашем телеграм-канале. Подписывайтесь!