потому что нельзя просто взять redux и начать использовать в React. Необходима некая «react-redux» библиотека. А в чём тогда смысл талдычить, что «Redux works with any UI layer»?
На фронт-собесах часто спрашивают: «Как хранить состояние в крупных react-проектах?» Отвечают заученное: можно сделать редуктор в контексте через useContext и useReducer.
Приходишь домой, думаешь: «Сейчас подключу разок, чисто пройти путь, подучиться». Ставишь redux в свой проект, грамотно всё подключаешь: actions, reducers, events. В итоге — консоль обновляется; интерфейс мёртв. ?
В прошлой статье я вывел Redux из конечных автоматов и закончил обещанием: «этот switch-case уже можно вынести в отдельную библиотеку». Сейчас попробую вынести и проверить два мифа:
-
Миф №1. Redux как-то по-особому хранит состояние: надёжно и без перерисовок.
Не то что тупое замыкание в JS или контекст в React!
-
Миф №2. Redux — независимая библиотека.
Её можно включить в любой проект и «просто» использовать.
День 1. Выносим switch-case в библиотеку
Редуктор (reducer) — функция переходов конечного автомата — остался с прошлой статьи. Проигрыватель, три состояния, переходы между ними:

// Чистая функция переходов: старое состояние + действие = новое состояние. // Ей не нужны ни DOM, ни библиотеки — поэтому она работает где угодно function редуктор(состояние = начальноеСостояние, действие) { // Поле называем «тип»: настоящий Redux требует type, но это наша поделка switch (действие.тип) { case "ИГРАТЬ": return { статус: СОСТОЯНИЯ.ИГРАЕТ }; case "ПАУЗА": return состояние.статус === СОСТОЯНИЯ.ИГРАЕТ ? { статус: СОСТОЯНИЯ.ПАУЗА } : состояние; case "СТОП": return { статус: СОСТОЯНИЯ.ОСТАНОВЛЕН }; default: // Неизвестное действие не должно ломать автомат return состояние; } }
Чтобы вынести это в отдельную «библиотеку», нужно придумать, где хранить состояние. Необходимо некое хранилище (store).
Требования короткие:
хранить состояние,
обрабатывать действия (action),
сообщать об изменениях.
Последний пункт — шаблон «Наблюдатель», тот же, что в статье про Jotai.
function createStore(редуктор, начальноеСостояние) { // Вся «магия» хранения Redux — это переменная в замыкании. // Второй аргумент — как у настоящего createStore(reducer, preloadedState) var состояние = начальноеСостояние; // Шаблон «Наблюдатель»: список подписчиков, которым сообщаем об изменениях var слушатели = new Set(); function getState() { return состояние; } function dispatch(действие) { состояние = редуктор(состояние, действие); слушатели.forEach(function (слушатель) { слушатель(); }); return действие; } function subscribe(слушатель) { слушатели.add(слушатель); // Возвращаем функцию отписки — её час настанет на третьем Дне return function () { слушатели.delete(слушатель); }; } return { getState, dispatch, subscribe }; }
Проверяем на чистом ECMAScript:
var хранилище = createStore(редуктор, начальноеСостояние); хранилище.subscribe(function () { console.log("Состояние:", хранилище.getState().статус); }); хранилище.dispatch({ тип: "ИГРАТЬ" }); // Состояние: ? Играет хранилище.dispatch({ тип: "ПАУЗА" }); // Состояние: ⏸️ Пауза
Работает! Но это вообще похоже на настоящий Redux?
Открываем redux/src/createStore.ts и видим:
строка 137:
let currentStateстроки 175–186: весь
getState— этоreturn currentStateиз замыкания ?строка 299:
currentState = currentReducer(currentState, action)— весьdispatch
Остальное в файле — проверки аргументов, сообщения об ошибках и служебные методы.
Ни особых структур данных, ни блатных каналов хранения данных (типа document.saveValueForRedux777(value)) — никакой магии… ?♂️
Одно отличие всё же есть: настоящий Redux требует, чтобы поле действия называлось type — без него dispatch бросает ошибку. Единственное слово, которое ему по-настоящему дорого. Нашей поделке никто не указ — поле зовётся «тип».
Redux хранит свой store через тупейшее замыкание!
Миф №1 развенчан. Попробуем подключить к React.
День 2. Подключаем к React
Я читал, что «Redux works with any UI layer». Просто подключу библиотеку и начну использовать.
Компоненты будут читать store.getState(), кнопки отправлять действия через dispatch. Что может пойти не так?
export function Проигрыватель() { // Читаем состояние напрямую. Один раз. При первой отрисовке var статус = хранилище.getState().статус; return ( <dl> <dt>Проигрыватель: {статус}</dt> <dd> <button onClick={() => хранилище.dispatch({ тип: "ИГРАТЬ" })}> Играть </button> <button onClick={() => хранилище.dispatch({ тип: "ПАУЗА" })}> Пауза </button> </dd> </dl> ); }
Кнопки нажимаются — это видно в консоли, но сайт не реагирует ☹️
Почему? Потому что у React своя движуха с жизненным циклом. Любая переменная, объявленная в React, не становится от этого react'ивной!
Я подробно рассказывал на Степике, но суть в том, что реактивность берётся как раз из функции setState. Мы буквально сами прописываем в своих событиях setState computeAndRenderComponent.
Так что означает «фреймворк-независимый»? На бытовом языке слышится «работает где угодно». На деле — «сам по себе не работает нигде, к каждому фреймворку нужен переходник». Миф №2 тоже потёк… ?
День 3. Пишем переходник сами
Вспомните День 1. Мы заложили в createStore метод subscribe — шаблон «Наблюдатель». Тогда он выглядел лишней церемонией: единственным подписчиком был console.log. Настал его час. Подпишем React на хранилище — осталось решить, где именно подписываться.
Попытка 1. В лоб: прямо в теле компонента.
export function ПроигрывательНаивный() { var [статус, установитьСтатус] = useState(хранилище.getState().статус); // Тело компонента выполняется на каждой отрисовке. // Каждая отрисовка добавляет ещё одну подписку — лавина хранилище.subscribe(function () { установитьСтатус(хранилище.getState().статус); }); return <dt>Проигрыватель: {статус}</dt>; }
Интерфейс ожил! А теперь откройте консоль и посчитайте подписки. Тело компонента выполняется на каждой отрисовке: обновление вызвало установитьСтатус, тот — новую отрисовку, та — новую подписку. Лавина.
Попытка 2. Подписка — побочный эффект. React выделил для них useEffect.
export function ПроигрывательСЭффектом() { var [статус, установитьСтатус] = useState(хранилище.getState().статус); useEffect(function () { // Пустой список зависимостей: подписываемся один раз, а не на каждой отрисовке var отписаться = хранилище.subscribe(function () { установитьСтатус(хранилище.getState().статус); }); // Компонент умрёт — подписка не должна жить дольше него return отписаться; }, []); return <dt>Проигрыватель: {статус}</dt>; }
Грабли здесь одни, и они снова про жизненный цикл: компонент можно размонтировать, а подписка сама не умрёт — хранилище продолжит дёргать установитьСтатус несуществующего компонента. Поэтому возвращаем из эффекта ту самую функцию отписки, которую заложили в subscribe ещё в Дне 1.
Попытка 3. В приложении компонентов много, и не каждому нужно всё состояние целиком. Выносим подписку в свой хук и добавляем селектор (selector) — функцию «какой кусок состояния слушаем»:
export function useSelector(селектор) { var [выбранное, установитьВыбранное] = useState(function () { return селектор(хранилище.getState()); }); useEffect( function () { // useState сравнивает значения: если селектор вернул то же самое, // React пропустит перерисовку — чужие изменения нас не волнуют var отписаться = хранилище.subscribe(function () { установитьВыбранное(селектор(хранилище.getState())); }); return отписаться; }, [селектор], ); return выбранное; }
В компоненте остаётся строка var статус = useSelector(выбратьСтатус);. Схема переходника целиком:

Одна тонкость напоследок: селектор объявляем снаружи компонента. Иначе на каждой отрисовке родится новая функция, useEffect увидит «новую» зависимость и будет переподписываться без конца.
Получается уже нехилая библиотека! Вот бы кто-нибудь написал её за нас…
День 4. Машина времени: лето 2015
Так написал же! И пришёл к ней ровно нашим путём — коммит за коммитом.
30 мая 2015 — Дэн Абрамов делает первый коммит в репозиторий redux. Никакого манифеста о независимости: это эксперимент вокруг Flux и горячей замены модулей (hot module replacement).
И главное: внутри redux с рождения живёт папка src/components. В ней Connector, Provider, декораторы @provides и @connect — обычные React-компоненты для связки логики redux и среды, где она живёт. Redux зарождался не «независимой библиотекой». Redux зарождался React-библиотекой!
24 июня 2015 — коммит «subscribe moved from Connector constructor to componentDidMount». Переведу с классового на хуковый: конструктор выполняется до того, как компонент попал на страницу, а componentDidMount — сразу после. Подписка в конструкторе — подписка не в той точке жизненного цикла: обновления летят в компонент, которого на странице ещё нет. Наши грабли из Дня 3, только на классах: мы точно так же переносили subscribe из тела компонента в useEffect — в «после отрисовки». Дэн делал то же самое десять лет назад.
11 июля 2015 — день переезда. В 19:04 по Гринвичу в новом репозитории появляется первый коммит: «Initial import from gaearon/redux» — React-код переезжает в отдельный репозиторий. И только в 20:36 по Гринвичу коммит c46494b0 удаляет его из самого redux: «Remove React-specific code in favor of gaearon/redux-react». На следующий день новый репозиторий переименуют: redux-react → react-redux.
«Фреймворк-независимость» Redux — не архитектурное решение!
Шесть недель React-код жил внутри redux — потому что без него библиотека в React бесполезна. Независимость не задумывалась. Её создали — вечером 11 июля, за полтора часа.
День 5. Что там сейчас
Десять лет спустя открываем современный react-redux/src/hooks/useSelector.ts:
import { useSyncExternalStoreWithSelector } from "use-sync-external-store/with-selector.js";
Команда React сделала официальный хук для внешних хранилищ — useSyncExternalStore. Отдаёте ему subscribe и функцию чтения — он сам подписывается, перечитывает состояние и отписывается. Наш День 3 сжимается в один вызов:
// хук сам подписывается, перечитывает состояние и отписывается export function useSelector(селектор) { return useSyncExternalStore(хранилище.subscribe, function () { return селектор(хранилище.getState()); }); }
Этот хук — преемник библиотеки use-subscription, на которой держался разбор Jotai. Круг замкнулся: redux + react-redux и Jotai решают одну задачу — подписать компоненты на внешнее хранилище — одним и тем же механизмом. Разница лишь в том, где живёт состояние: у Redux — всё состояние в одном объекте, у Jotai — россыпь атомов.
Рабочие файлы всех этапов — в репозитории автора [Место для ссылки: папка redux-этапы].
Вывод. Если всё так сложно — зачем?
Redux хранит состояние в обычной переменной. let currentState в замыкании, строка 137 файла createStore.ts. Redux Toolkit — обёртка над ней же.
«Фреймворк-независимость» — миф! Redux был обычной React библиотекой. Просто в какой-то момент ядро вынесли в отдельный пакет.
Без react-redux Redux в React не работает. React не следит за чужими переменными. Любому внешнему хранилищу нужен переходник — сегодня это официальный хук useSyncExternalStore.
И вот мысль, что не даёт мне покоя: Если redux'у для работы с React всё равно нужна ещё одна библиотека — зачем париться и разделять? Какая мне-то выгода от «независимости», если для React я ставлю два пакета? Может, проще взять какое-то react-решение по умолчанию? Что-то родное? Что-то, что писали не для Angular, Vue и, может, React на сдачу? Jotai, например — я разбирал его полностью.
Откуда я это вычитал? Из git log самих библиотек. Клонируете redux и react-redux, листаете коммиты лета 2015 — и видите настоящие концепции вместо инфлюенсерских пересказов, провёрнутых через ИИ.
Магии вокруг хранения состояния на фронте столько, что ни ютуберы, ни ИИ уже не могут ответить как по git log коммитам
Синтаксис — ничто, понимание концепций — всё!
Если вы есть, будьте первыми! ?
Список литературы
createStore.ts // GitHub, reduxjs/redux. URL: github.com/reduxjs/redux (дата обращения: 05.07.2026).
Commit c46494b0 «Remove React-specific code in favor of gaearon/redux-react» // GitHub, reduxjs/redux. URL: github.com/reduxjs/redux (дата обращения: 05.07.2026).
Commit 52a2801 «Initial import from gaearon/redux» // GitHub, reduxjs/react-redux. URL: github.com/reduxjs/react-redux (дата обращения: 05.07.2026).
useSyncExternalStore // React. URL: react.dev (дата обращения: 05.07.2026).
Micro State Management with React Hooks / Daishi Kato // Packt. URL: packtpub.com (дата обращения: 05.07.2026).
Зачем Тьюринг изобрёл Redux? // Хабр. URL: habr.com (дата обращения: 05.07.2026).
Как использовать глобальное состояние в React.js? // Хабр. URL: habr.com (дата обращения: 05.07.2026).
TheHost
У Дена абрамова есть целый курс по написанию редакса. Редакс действительно можно использовать с разными фреймворками, на беке и так далее. Это всего лишь грубо говоря паттерн.