Redux — это библиотека для управления состояниями, которая следует принципам поточной архитектуры. Мы не будем слишком углубляться в технические детали, но рассмотрим основные принципы, которые вам понадобятся для понимания этой структуры. Redux — это независимый фреймворк, но для краткости будем считать, что используем его с React.
Глобальное состояние (магазин)
В отличие от стандартной архитектуры React, состояние компонентов не хранится в его классе компонентов (за исключением особых случаев). Все данные из представления или полученные API, базами данных или любой другой конечной точкой хранятся в одном глобальном объекте, называемом хранилищем (его также часто называют состоянием).
Цель хранилища — связать каждое свойство компонента с одним из его свойств. Таким образом, каждый раз, когда данные свойства обновляются, компонент снова отображается с использованием новых данных. Следуя этой концепции, давайте предположим, что у нас есть приложение, которое требует аутентификации пользователя и функций списка дел. В этом случае наш магазин будет выглядеть примерно так:
{ auth: { username: 'admin', name: 'John Doe' }, todos: [ 'Brew coffee', 'Read Redux article', 'Watch AI documentary' ], }
Обратите внимание, что мы сохранили данные о двух разных функциях в одном объекте.
Действия
Действия — это полезные данные, которые отправляют данные из вашего приложения в ваш магазин. Они являются единственным источником информации для магазина.
Это означает, что каждый раз, когда вам нужно изменить данные свойства в хранилище, вам нужно отправить действие. В более техническом описании действия — это объекты, которые содержат свойство типа (отвечающее за информирование хранилища о том, какое действие оно должно выполнять) и полезную нагрузку (фактические данные, которые необходимо изменить).
// 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 в полной мере.