State Management
Flux и reducer-модель
В приложении с двусторонней привязкой счётчик корзины обновлялся из пяти разных мест: при добавлении товара, при синхронизации с сервером, при сбросе фильтра и ещё дважды. Когда число оказалось неверным, отследить виновника было нельзя - данные текли во все стороны. Flux отвечает на это одним жёстким правилом: данные текут только в одну сторону. Любое изменение это действие (action), оно проходит через чистый редьюсер, тот возвращает новое состояние, и только потом обновляется view. Никаких обратных путей. Из этого правила и выросла предсказуемость Redux.
- Корзина, обновляемая из многих мест, где важно отследить, какое действие изменило состояние
- Devtools с историей действий и перемоткой состояния назад и вперёд для отладки
- Крупное приложение, где строгий поток ценнее краткости записи
- Логирование и аналитика, подписанные на поток действий как на единый журнал событий
- Redux Toolkit как современный официальный способ писать reducer-логику с меньшим объёмом кода
Предварительные знания
- Обзор парадигм реактивности и место flux/reducer на карте
- Иммутабельные обновления: новое состояние вместо мутации прежнего
- Идея чистой функции: одни входы дают один выход без побочных эффектов
Однонаправленный поток как ответ на двустороннюю привязку
Flux представила Джинг Чен на конференции Facebook F8 в 2014 году. Идея родилась из боли двусторонней привязки: когда вид меняет модель, а модель меняет вид, поток данных превращается в граф без понятного направления, и причину рассинхрона не отследить. Flux ввёл одно направление: action идёт в dispatcher, dispatcher в store, store обновляет view, и обратной стрелки нет. В 2015 году Дэн Абрамов упаковал эти идеи в Redux: один store, чистые редьюсеры, чистые действия. Главным подарком стали devtools с перемоткой состояния - возможной только потому, что каждый переход состояния детерминирован и явен. К 2026 году официальный способ работы с Redux это Redux Toolkit, который убирает большую часть прежнего boilerplate.
Однонаправленный поток данных
Сердце Flux это однонаправленность. Данные движутся по кругу строго в одну сторону: представление порождает action, action попадает в редьюсер, редьюсер вместе с текущим состоянием даёт новое состояние, store его сохраняет, и представление перерисовывается из нового состояния. Представление никогда не меняет состояние напрямую - только через action. Это и убирает запутанный граф двусторонней привязки.
Польза одного направления в прослеживаемости. Раз состояние меняется только через action, проходящие через единую точку, всегда понятно, что именно вызвало изменение. Можно записать поток action в журнал, воспроизвести его, отмотать назад. Цена этого - больше явного кода: каждое изменение нужно выразить как action и обработать в редьюсере.
Ключевой запрет: представление не пишет в состояние напрямую. Оно только читает текущее состояние и отправляет action. Любая попытка обойти этот запрет и поменять состояние в обход редьюсера разрушает гарантии прослеживаемости и предсказуемости.
Что означает однонаправленный поток данных в Flux?
Чистые редьюсеры
Редьюсер это чистая функция вида (state, action) => newState. Чистая означает три вещи: результат зависит только от аргументов, никаких побочных эффектов (сети, таймеров, записи в глобальные переменные) и никакой мутации входного состояния. Редьюсер не меняет прежнее состояние, а возвращает новое, иммутабельно. Эти свойства и делают переходы детерминированными и воспроизводимыми.
Внутри редьюсера недопустимы: запрос к серверу, обращение к Date.now или Math.random, мутация state.items.push. Любое из этого делает результат недетерминированным или скрыто меняет вход, и тогда перемотка состояния в devtools и воспроизведение действий перестают работать корректно.
- Чистый редьюсер — Зависит только от (state, action), возвращает новое состояние иммутабельно, без эффектов. Один и тот же вход всегда даёт один и тот же выход - переходы воспроизводимы.
- Загрязнённый редьюсер — Делает запрос, читает текущее время или мутирует вход. Результат непредсказуем, devtools-перемотка ломается, баги трудно воспроизвести.
Куда же тогда деваются побочные эффекты, например запросы к серверу? Они выносятся за пределы редьюсера - в middleware или эффекты (Redux Thunk, Redux Saga, listener middleware). Редьюсер остаётся чистым и отвечает только за то, как состояние переходит из старого в новое по уже случившемуся действию.
Почему редьюсер обязан быть чистой функцией без побочных эффектов и мутаций?
Store, action и роль Redux Toolkit
Action это простой объект, описывающий что произошло: поле type (например cart/itemAdded) и обычно payload с данными. Store держит каноническое состояние, прогоняет каждое действие через редьюсер и уведомляет подписчиков. View отправляет действия через dispatch и читает состояние через селекторы. Три роли - action, reducer, store - и составляют классический Redux.
Классический Redux требовал много ручной обвязки: константы типов, генераторы action, разнесённые файлы. Redux Toolkit, официальный современный способ, сворачивает это в createSlice: действия и редьюсеры объявляются вместе, а внутри редьюсеров можно писать как бы мутативный код, потому что Toolkit использует Immer и под капотом обновляет состояние иммутабельно.
Парадигма та же: однонаправленный поток и редьюсеры. Redux Toolkit меняет не модель, а эргономику - убирает boilerplate и встроенно подключает Immer. Поэтому в 2026 году писать чистый Redux вручную почти не нужно: официальная рекомендация это Redux Toolkit.
Что Redux Toolkit меняет по сравнению с классическим ручным Redux?
Связь с другими темами
Reducer-модель опирается на иммутабельность и соседствует с родственными подходами:
- Иммутабельность и нормализация — Чистый редьюсер обязан вернуть новое состояние иммутабельно, иначе обнаружение изменений и devtools ломаются
- Zustand — То же семейство стора, но без обязательных action-объектов и dispatch: действия это обычные функции
- NgRx SignalStore — Перенос flux/reducer-потока в Angular поверх сигналов
Итог
- Flux вводит однонаправленный поток данных: action -> reducer -> store -> view, без обратных путей
- Action это простой объект, описывающий что произошло, обычно с полями type и payload
- Reducer это чистая функция (state, action) => newState без побочных эффектов и без мутаций
- Один store держит каноническое состояние, а view лишь читает его и порождает новые action
- Детерминированность переходов делает возможными devtools с историей и перемоткой состояния
- Redux Toolkit это официальный современный способ писать ту же модель с гораздо меньшим объёмом кода
Связанные уроки
- sm-06-paradigms-overview — Flux/reducer это первая парадигма из общей карты, теперь подробно
- sm-05-immutability-normalization — Чистый редьюсер обязан возвращать новое состояние иммутабельно, не мутируя прежнее
- rc-38-zustand-state — Zustand из того же семейства, но без обязательных action и dispatch
- ng-39-ngrx-signalstore — NgRx переносит flux/reducer-поток в экосистему Angular