JS: Redux (React)

Редьюсеры

Все, что хранится в контейнере, мы называем состоянием, но не все состояния одинаково полезны. Вот какую классификацию вводит документация Redux:

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

Так как контейнер представляет собой ядро приложения, данные внутри него должны описываться в терминах domain data и app state, но не как дерево компонентов UI. Например, такой способ формирования состояния state.leftPane.todoList.todos — плохая идея. Крайне редко дерево компонентов отражается напрямую на структуру состояния, и это нормально. Представление зависит от данных, а не данные от представления.

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

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

Подробнее про работу с состоянием UI будет рассказано в соответствующем уроке.

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

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

С такой структурой крайне легко писать реакцию на действия, обновлять данные, добавлять новые и удалять старые. Вложенность небольшая, всё просто. Но появляется другая проблема (появляется она в любом случае). С ростом количества сущностей, редьюсер становится очень тяжёлым. Огромный кусок кода, который делает всё. Для решения этой проблемы, Redux имеет встроенный механизм, позволяющий создавать множественные редьюсеры и комбинировать их друг с другом. Работает это так: для каждого свойства верхнего уровня пишется свой собственный редьюсер, а затем они с помощью функции combineReducers объединяются в корневой (root) редьюсер, который уже используется для создания контейнера.

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, но это не всё состояние контейнера, а только та часть, которая лежит в соответствующем свойстве. Не забудьте про это.

Часто бывает нужно инициализировать контейнер данными, например, пришедшими от бекенда. Функция createStore принимает второй параметр, который как раз выполняет эту задачу:

const initState = {
  comments: [...],
  todos: [...]
}

const store = createStore(rootReducer, initState);

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

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

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

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

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

То есть правильный подход состоит в том, чтобы повторять часть case в нужных редьюсерах, а не в том, чтобы пытаться получить недостающие части состояния.


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

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

<span class="translation_missing" title="translation missing: ru.web.courses.lessons.mentors.mentor_avatars">Mentor Avatars</span>

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

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

Ошибки, сложный материал, вопросы >
Нашли опечатку или неточность?

Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.

Что-то не получается или материал кажется сложным?

Загляните в раздел «Обсуждение»:

  • задайте вопрос. Вы быстрее справитесь с трудностями и прокачаете навык постановки правильных вопросов, что пригодится и в учёбе, и в работе программистом;
  • расскажите о своих впечатлениях. Если курс слишком сложный, подробный отзыв поможет нам сделать его лучше;
  • изучите вопросы других учеников и ответы на них. Это база знаний, которой можно и нужно пользоваться.
Об обучении на Хекслете

Для полного доступа к курсу нужна профессиональная подписка

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

Получить доступ
115
курсов
892
упражнения
2241
час теории
3196
тестов

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

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

  • 115 курсов, 2000+ часов теории
  • 800 практических заданий в браузере
  • 250 000 студентов

Отправляя форму, вы соглашаетесь c «Политикой конфиденциальности» и «Условиями оказания услуг».

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

Логотип компании Альфа Банк
Логотип компании Rambler
Логотип компании Bookmate
Логотип компании Botmother

Есть вопрос или хотите участвовать в обсуждении?

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

Отправляя форму, вы соглашаетесь c «Политикой конфиденциальности» и «Условиями оказания услуг».