Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Редьюсеры React: Redux Toolkit

Мы называем состоянием все, что находится в хранилище. При этом не все состояния одинаково полезны. Документация Redux предлагает такую классификацию:

  • Domain data — это данные, которые нужно отображать, использовать и модифицировать (например, загруженный с сервера список пользователей)
  • App state — данные, которые определяют поведение приложения (например, открытый URL)
  • UI state — данные, которые определяют внешний вид (например, вывод списка в виде плиток)

Хранилище — это ядро приложения, поэтому данные внутри него должны описываться в терминах domainData и appState, а не как дерево компонентов UI.

Например, способ формирования состояния state.leftPane.todoList.todos — плохая идея. Очень редко дерево компонентов отражается напрямую на структуру состояния, и это нормально. Представление зависит от данных, а не данные от представления.

Типичная структура состояния выглядит так:

{
    domainData1 : {}, // todos
    domainData2 : {}, // comments
    appState1 : {},
    appState2 : {},
    uiState1 : {},
    uiState2 : {},
}

Как уже говорилось в курсе JS: React, структура состояния должна напоминать базу данных. Все максимально плоско и нормализованно:

{
  todos: [
    { id: 1, name: 'why?' },
    { id: 3, name: 'who?' },
  ],
  comments: [
    { id: 23, todoId: 3, text: 'great!' },
  ],
}

С такой структурой намного проще писать реакции на действия, добавлять новые данные и удалять старые. Вложенность небольшая, поэтому все просто. Но появляется другая проблема — чем больше сущностей, тем тяжелее становится редьюсер. Постепенно он превращается в огромный кусок кода, который делает все.

Чтобы решить эту проблему, можно использовать механизм, встроенный в Redux. Он позволяет создавать множественные редьюсеры и комбинировать их друг с другом. Работает это так:

  • Для каждого свойства верхнего уровня пишется свой редьюсер
  • С помощью функции combineReducers() все редьюсеры объединяются в корневой редьюсер (root)
  • Корневой редьюсер используется для создания хранилища

В итоге действия попадают во все редьюсеры, собранные внутри combineReducers():

import { combineReducers, createStore } from 'redux';

const todosReducer = (state = [], action) => {
  // Сюда попадут данные из todos
};

const commentsReducer = (state = [], action) => {
  // Сюда попадут данные из comments
};

const rootReducer = combineReducers({
  todos: todosReducer,
  comments: commentsReducer,
});
const store = createStore(rootReducer);

// Если назвать редьюсеры как свойства в состоянии, то код сократится:
// const todos = (state = [], action) => { ... };
// const comments = (state = [], action) => { ... };
// const rootReducer = combineReducers({ todos, comments });

В каждый редьюсер приходит state, но это не все состояние хранилища, а только та часть, которая лежит в соответствующем свойстве. В примере выше в редьюсер todosReducer будет передаваться состояние из свойства todos, а в commentsReducer из comments. Эта привязка происходит через объект, который передается в combineReducers():

combineReducers({
  todos: todosReducer, // редьюсер привязывается к свойству todos глобального состояния
  comments: commentsReducer, // редьюсер привязывается к свойству comments глобального состояния
});

Схематично комбинацию редьюсеров можно обозначить так:

Комбинация редьюсеров Redux

А еще редьюсеры могут быть даже вложенными. Для этого не нужны никакие специальные средства — это обычные функции, принимающие одни данные и возвращающие другие данные.

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

const todos = (state = {}, action) => {
  switch (action.type) {
    case 'TODO_REMOVE':
      // ...
  }
};

const comments = (state = {}, action) => {
  switch (action.type) {
    // При удалении ToDo нужно удалить все его комментарии
    case 'TODO_REMOVE':
      // ...
  }
};

Правильный подход в этом случае — повторять часть case в нужных редьюсерах.


Дополнительные материалы

  1. Нормализация структуры состояния

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Об обучении на Хекслете

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 6 300 ₽ в месяц
Разработка фронтенд-компонентов для веб-приложений
10 месяцев
с нуля
Старт 25 апреля
профессия
от 9 900 ₽ в месяц
Разработка фронтенд- и бэкенд-компонентов для веб-приложений
16 месяцев
с нуля
Старт 25 апреля

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»