Redux — это библиотека для управления состояниями, которая следует принципам поточной архитектуры. Мы не будем слишком углубляться в технические детали, но рассмотрим основные принципы, которые вам понадобятся для понимания этой структуры. Redux — это независимый фреймворк, но для краткости будем считать, что используем его с React.

Глобальное состояние (магазин)

В отличие от стандартной архитектуры React, состояние компонентов не хранится в его классе компонентов (за исключением особых случаев). Все данные из представления или полученные API, базами данных или любой другой конечной точкой хранятся в одном глобальном объекте, называемом хранилищем (его также часто называют состоянием).

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

{
  auth: {
    username: 'admin',
    name: 'John Doe'
  },
  todos: [
    'Brew coffee',
    'Read Redux article',
    'Watch AI documentary'
  ],
} 

Обратите внимание, что мы сохранили данные о двух разных функциях в одном объекте.

Действия

Действия — это полезные данные, которые отправляют данные из вашего приложения в ваш магазин. Они являются единственным источником информации для магазина.

- redux.js.org

Это означает, что каждый раз, когда вам нужно изменить данные свойства в хранилище, вам нужно отправить действие. В более техническом описании действия — это объекты, которые содержат свойство типа (отвечающее за информирование хранилища о том, какое действие оно должно выполнять) и полезную нагрузку (фактические данные, которые необходимо изменить).

// Action example
{
  type: 'LOGIN',
  payload: {
    username: 'admin',
    password: '123456'
  },
}

Рекомендуется хранить все данные (кроме типа) в одном свойстве, называемом полезной нагрузкой.

Все еще думая о функции аутентификации, мы могли бы реализовать три действия: LOGIN_REQUEST, LOGIN_SUCCESS и LOGIN_ERROR.

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

Чтобы отправить действие в нашем приложении, нам нужно сделать его возвращаемым объектом чистой функции (создатель действия). Эта функция будет доступна для каждого компонента приложения.

const authUser = (username, password) => ({
  type: 'LOGIN_REQUEST',
  payload: {
    username,
    password
  }
});

Редукторы

Хотя действия могут прослушивать события, инициированные пользователем или приложением, они не могут изменить состояние нашего хранилища. Чтобы внести какие-либо изменения в магазин, нам нужно использовать наших посредников, редьюсеров.

Каждый редуктор слушает каждое действие, отправленное приложением. От нас зависит, какие из этих действий необходимо учитывать и с чем что-то делать. Для функции аутентификации у нас будет редьюсер для работы с данными аутентификации пользователя:

const authenticated = (state = false, action) => {
  switch(action.type) {
    case 'LOGIN_SUCCESS':
      return true;
    case 'LOGOUT'
      return false;
    default:
      return state;
  }
}

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

Общепринятой практикой является использование оператора switch для оценки типа действия и соответствующего возврата значения. Это то, что гарантирует, что наш редуктор изменяет состояние хранилища только с определенными действиями и возвращает значение по умолчанию для других, сохраняя целостность состояния.

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

// State representation
{
  auth: {
    authenticated: true,
  },
  user: {
    username: 'admin',
    name: 'John Doe'
  },
  token: 'somecooltoken',
  // ... other properties
}

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

В заключение, это основы, которые вам необходимо понять для работы с Redux, но по мере роста сложности вашего приложения вы можете использовать еще более продвинутые функции, такие как, например, промежуточное ПО (Redux Thunk).

Я надеюсь, что это было полезное знакомство с Redux, и что с этого момента вы почувствуете больше мотивации использовать Redux в полной мере.