Redux: что это такое и зачем она нужна
2026-02-21 22:26 Diff

#статьи

  • 26 дек 2025
  • 0

Управляем состоянием приложения одной библиотекой.

Иллюстрация: Оля Ежак для Skillbox Media

Пишет о сетях, инструментах для разработчиков и языках программирования. Любит готовить, играть в инди‑игры и программировать на Python.

Redux — это библиотека для управления состоянием данных и пользовательским интерфейсом в JavaScript-приложениях. Её используют, когда данных становится много и они меняются в разных компонентах ПО: это потенциально может привести к проблемам, например к использованию в функциях устаревших значений переменных.

Из этой статьи вы узнаете, как работать с Redux и из каких компонентов состоит библиотека. Также мы напишем простое приложение, отработав теорию на практике. Статья будет полезна в первую очередь начинающим фронтендерам, работающим с React.

Содержание

Redux используют в приложениях с большим объёмом данных, которые часто обновляются и используются в разных частях интерфейса. В такой ситуации сложно поддерживать согласованность состояния программы вручную: отдельные функции могут обращаться к устаревшим значениям, а интерфейс — вести себя непредсказуемо.

Ключевой термин в абзаце выше, который важно знать, чтобы оценить преимущества Redux, — это состояние. Под ним понимают единый объект данных, который описывает текущее состояние всего приложения в конкретный момент времени. Можно сказать, что это «снимок» того, что сейчас знает о себе программа: значения переменных, активные элементы интерфейса, пользовательский ввод и так далее.

Redux решает проблему поддержания согласованности состояния приложения за счёт строгого набора правил. Библиотека однозначно указывает, кто может инициировать изменение данных, когда это допустимо и по каким правилам они обновляются. Ключевой принцип в том, что любой компонент в приложении не имеет права сам менять данные. Он может только запросить изменение, отправив об этом сообщение.

Такой подход основан на Flux-архитектуре. Чтобы разобраться в ней, сначала посмотрим на то, как происходит обмен данными в классической MVC-архитектуре.

В традиционном подходе компоненты могут сами менять данные, а связи между ними часто двусторонние. Это создаёт проблему, так как число взаимодействий с ростом сложности приложения растёт и в нём появляются неочевидные зависимости. В итоге изменение в одном компоненте меняет интерфейс, интерфейс влияет на другой компонент, тот — на третий и так далее. Всё это делает работу приложения с MVC-архитектурой непредсказуемой, усложняет отладку и масштабирование.

MVC-архитектура: классический подход в разработке
Инфографика: Майя Мальгина для Skillbox Media

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

Каждое хранилище отвечает за свою часть данных, а состояние приложения распределено между ними и обновляется по единым правилам.

Flux-архитектура
Инфографика: Майя Мальгина для Skillbox Media

Redux — одна из практических реализаций Flux-архитектуры с важным отличием: на смену нескольким хранилищам (Store) приходит одно.

Но ради надёжности приходится жертвовать удобством — весь многоступенчатый процесс изменения состояния требуется прописывать для любых действий. Если для важных процедур вроде изменения баланса это оправдано, то для какой-нибудь мелочи (выпадающего окна или изменения цвета кнопки) — уже излишне.

Проблему такой «бюрократии» частично решает Redux Toolkit (RTK) — набор инструментов от создателей библиотеки. RTK автоматизирует рутинные части процесса и позволяет разработчикам обойтись меньшим объёмом кода. О нём мы подробно поговорим в практической части статьи.

Перейдём от концепции к реализации. Redux не вводит новые абстракции, а только задаёт строгие правила взаимодействия обычных сущностей JavaScript. В библиотеке есть шесть ключевых элементов: store, state, action, reducer, dispatch и subscribe.

В примерах кода в этом разделе мы будем использовать приложение интернет-провайдера с одним абонентом. Это поможет лучше понять компоненты библиотеки.

Store — главный объект, который связывает все части Redux воедино. Он хранит состояние, предоставляет к нему доступ, позволяет его изменять и подписываться на изменения тем компонентам, которых эти изменения затрагивают.

В коде это JavaScript-объект, который создаётся функцией createStore() при запуске приложения и остаётся в единственном экземпляре во время его работы.

const store = createStore(reducer);

createStore() принимает в качестве аргумента reducer — функцию, которая содержит конкретные операции. Необязательным вторым параметром является изначальное состояние хранилища — массив значений для компонентов. Например, такой:

let initialState = { user: null, isLoading: false, posts: [] };

У store есть два важных метода — dispatch и subscribe. dispatch() — это метод хранилища, который принимает в качестве аргумента действие и передаёт его редьюсеру.

store.dispatch(action)

subscribe() — это метод, который передаёт компоненту новое состояние приложения. Без него фактические изменения не отображались бы в интерфейсе.

Метод принимает функцию-слушателя listener, которая будет автоматически запускаться каждый раз, когда кто-то вызывает dispatch:

store.subscribe(listener)

State — это снимок состояния приложения в конкретный момент времени. Он хранит данные, необходимые для отслеживания текущего статуса, — число пользователей, суммарную стоимость товаров в корзине, баланс счёта и другие изменяющиеся со временем данные.

В коде это объект, который хранит массив значений для компонентов. В примере банковского приложения это выглядит так:

const state = { user: { name: "Alex", id: 1 }, balance = 0, isInternetActive: false };

State в Redux доступен только для чтения. Вы не сможете написать state.balance = 500. Единственный способ изменить его — создать новый стейт через цепочку действий.

Action описывает событие и то, как требуется изменить состояние в связи с ним. В коде это простой объект с двумя свойствами — обязательным type и необязательным payload:

const action = { // Действие с двумя свойствами type: "PAY_INTERNET", // Что случилось? Оплата интернета payload: 500 // Данные: сумма списания }; const action = { // Действие с одним свойством type: "RECONNECTION" // Что случилось? Переподключение };

Редьюсер определяет, как действие должно изменить состояние, и меняет его. В коде он представлен функцией, которая в качестве аргументов принимает текущее состояние и действие, а возвращает новое состояние. Так как редьюсер работает с разными действиями, то тело функции состоит из 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 проще понять на практике. В этом разделе мы напишем часть простого банковского приложения на React. В ней реализуем весь цикл: от создания хранилища до отрисовки кнопок «Пополнить» и «Снять» в интерфейсе.

Мы предполагаем, что React.js и Node.js у вас уже установлены. Если нет, то сначала установите Node.js, а уже с её помощью установите React.js.

В прошлых разделах мы подробно разобрали чистую 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 и упрощает работу с общим состоянием приложения по мере его роста.

В Props компонент может получить данные только от родительского компонента. <Provider> позволяет избежать этого, так как состояние доступно всем компонентам напрямую
Инфографика: Майя Мальгина для 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.

Курс с трудоустройством: «Профессия Разработчик + ИИ» Узнать о курсе