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
Flux и reducer-модель

0

1

Войти