Что такое Reducer в Redux?

Ответ

Reducer (редюсер) в Redux — это чистая функция, которая определяет, как состояние (state) приложения изменяется в ответ на отправленное действие (action). Это единственное место, где логика обновления состояния должна быть описана.

Сигнатура функции:

function reducer(previousState, action) {
  // Вычисляем и возвращаем следующее состояние
  return nextState;
}

Три фундаментальных принципа Reducer:

  1. Чистота: Не производит сайд-эффектов (вызовов API, мутаций).
  2. Иммутабельность: Не изменяет переданный previousState. Вместо этого создаёт и возвращает новый объект состояния.
  3. Детерминированность: Для одних и тех же входных state и action всегда возвращает одинаковый nextState.

Практический пример из моего опыта: Допустим, у нас есть состояние для управления списком задач (todo list).

// Начальное состояние
const initialState = {
  todos: [],
  filter: 'SHOW_ALL'
};

// Reducer
function todoApp(state = initialState, action) {
  switch (action.type) {
    case 'ADD_TODO':
      // ВАЖНО: Не мутируем старый массив, создаём новый
      return {
        ...state, // Копируем все поля предыдущего состояния
        todos: [
          ...state.todos, // Копируем старый массив todos
          { // Добавляем новый элемент
            id: action.payload.id,
            text: action.payload.text,
            completed: false
          }
        ]
      };

    case 'TOGGLE_TODO':
      return {
        ...state,
        todos: state.todos.map(todo =>
          // Если это не тот todo, возвращаем как есть
          todo.id !== action.payload.id ? todo :
          // Иначе создаём новый объект todo с изменённым полем
          { ...todo, completed: !todo.completed }
        )
      };

    case 'SET_FILTER':
      return {
        ...state,
        filter: action.payload.filter
      };

    // На действие по умолчанию возвращаем неизменённое состояние
    default:
      return state;
  }
}

Почему это важно: Такая архитектура делает изменения состояния предсказуемыми и легко тестируемыми. Вы можете логировать каждое действие и состояние «до/после», что неоценимо при отладке сложного взаимодействия в приложении.

Ответ 18+ 🔞

А, редюсеры, ёпта! Ну это ж классика, как водка с огурцом. Слушай сюда, я тебе сейчас на пальцах объясню, что это за зверь такой, а то некоторые думают, что это какой-то рик-ролл для программистов.

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

Что он делает, этот чувак? Проще говоря, он сидит и ждёт. Ждёт, пока ему принесут два документа:

  1. previousState — это как последняя версия баланса компании, состояние приложения на данный момент.
  2. action — это, сука, приказ сверху, бумажка с печатью, где написано «Добавить задачу» или «Пометить как выполненное».

И его работа — не накосячить. Три его главных заповеди, которые он нарушить не может, иначе его уволят к ебеням:

  1. Чистота, как у ангела. Никаких своих делишек! Не может он вдруг пойти и запрос на сервер отправить или время на компьютере поменять. Только бумажки, только хардкор.
  2. Иммутабельность, или «Руками не трогать!» Ему принесли старый баланс (state). Он его НИ В КОЕМ СЛУЧАЕ НЕ ПЕРЕЧЁРКИВАЕТ. Он берёт чистый бланк и аккуратненько, буковка в буковку, переписывает всё заново, внося ту единственную поправку из приказа (action). Старый документ остаётся неприкосновенным. Это, блядь, основа основ, запомни как «Отче наш».
  3. Детерминированность. Это значит, что если ты дашь ему один и тот же баланс и один и тот же приказ хоть сто раз — он сто раз выдаст тебе абсолютно одинаковый новый баланс. Никаких «ой, а сегодня пятница, я по-другому посчитал». Предсказуемость, блядь, наше всё.

Смотри, как это выглядит в жизни, на примере списка дел:

// Начальное состояние. Пусто, как твои карманы после зарплаты.
const initialState = {
  todos: [],
  filter: 'SHOW_ALL'
};

// Сам редюсер. Обрати внимание на switch-case — это его рабочий инструмент.
function todoApp(state = initialState, action) {
  switch (action.type) {
    // Кейс 1: Приказ "ДОБАВИТЬ_ЗАДАЧУ"
    case 'ADD_TODO':
      // ВАЖНО! Смотри, как он не трогает старый state.
      // Он создаёт новый объект, разворачивая в него всё из старого (...state).
      // Потом КОПИРУЕТ старый массив задач (...state.todos) и ДОБАВЛЯЕТ в конец новую.
      // Никакого push'а! Только копия и новый элемент.
      return {
        ...state,
        todos: [
          ...state.todos,
          {
            id: action.payload.id,
            text: action.payload.text,
            completed: false
          }
        ]
      };

    // Кейс 2: Приказ "ПЕРЕКЛЮЧИТЬ_ЗАДАЧУ"
    case 'TOGGLE_TODO':
      return {
        ...state,
        // Опять копия! map проходит по старому массиву и для каждой задачи проверяет:
        // Это та самая задача, которую нужно поменять? Если нет — возвращает её как есть.
        // Если ДА — создаёт НОВЫЙ объект задачи, копируя старые поля и меняя одно.
        todos: state.todos.map(todo =>
          todo.id !== action.payload.id ? todo :
          { ...todo, completed: !todo.completed }
        )
      };

    // Кейс 3: Приказ "УСТАНОВИТЬ_ФИЛЬТР" (показать все/только выполненные/только активные)
    case 'SET_FILTER':
      // Тут проще. Копируем state и просто меняем одно поле.
      return {
        ...state,
        filter: action.payload.filter
      };

    // А если принесли какую-то левую бумажку, которую он не понимает?
    // Он пожимает плечами и возвращает state в том виде, в котором получил. Без изменений.
    default:
      return state;
  }
}

И зачем весь этот цирк, спросишь ты? А затем, чувак, что это даёт тебе, разработчику, сверхспособности.

  1. Отладка — одно удовольствие. Ты можешь записывать (логгировать) каждый приказ (action) и состояние «до» и «после». Как в детективе: было так, произошло это, стало этак. Нашёл косяк? Просто отмотай лог назад и посмотри, на каком шаге твой бухгалтер-редюсер накосячил.
  2. Тестирование — раз плюнуть. Подсунул ему начальное состояние, подсунул тестовый приказ — получил результат. Никаких скрытых эффектов, никаких зависимостей от времени или базы данных. Всё чисто, как стерильная игла.
  3. Путешествие во времени (time-travel debugging). Это вообще магия. Поскольку у тебя сохраняется каждое состояние и каждый приказ, ты можешь буквально перематывать состояние приложения туда-сюда. Охуенно же!

Вот и вся философия. Не мутируй, копируй. Не делай сайд-эффектов, просто считай. Будь проще, как этот редюсер, и жизнь наладится. Ну, по крайней мере, жизнь твоего стейт-менеджмента.