Это первая статья из серии, в которой мы попытаемся выяснить, есть ли место для Redux по ту сторону забора.
Несмотря на то, что эта серия статей основана на предположении, что вы более или менее знакомы с Redux, не волнуйтесь, если нет, так как сначала мы рассмотрим необходимые концепции.
Как только мы будем уверены в Redux как в менеджере состояний React, мы будем изучать, как мы можем использовать его в качестве внутреннего инструмента управления состоянием и распределения состояний, и постепенно создавать концептуальный дизайн. Наконец, что не менее важно, мы запачкаем руки и создадим приложение с Redux на сервере.
Если вам суперкомфортно с Redux, смело начинайте с пустыни.
Очень краткая история
Redux появился в качестве доказательства концепции во время подготовки к конференции React Europe еще в 2015 году.
Я пытался проверить концепцию Flux, чтобы изменить логику. И это позволило бы мне путешествовать во времени. И это позволило бы мне повторно применить будущие действия к изменению кода.
Очень скоро Redux приобрел огромную популярность в сообществе фронтенда, это простая и удобная библиотека для управления состоянием. Redux делает многие сложные задачи тривиальными.
Государственное управление
Чтобы понять, что Redux может предложить, мы начнем с рассмотрения того, что может предложить чистый React.
React не поставляется с Redux из коробки, и на это есть причина. В большинстве случаев он вам, вероятно, не понадобится. В React существует способ управления распределением состояния и зависимостью от состояния. В React вы можете распространять состояние до компонентов верхнего уровня и возлагать на них ответственность за распределение и управление зависимостями. Поток состояний является однонаправленным и простым в управлении.
Думайте о приложении React как о простом фильтре для воды. Там, где состояние — вода, каждый слой — компонент. Мы очищаем воду в бутылке, вода последовательно проходит через каждый слой, каждый слой берет то, что нужно, и пропускает воду к следующему слою.
Надеюсь, идея понятна, но зачем и когда нужен Redux?
Вы поймете, когда вам понадобится Flux. Если вы не уверены, что вам это нужно, значит, оно вам не нужно.
– Пит Хант (один из первых участников React)
Мы можем применить то же правило к Redux. Если вы не уверены, нужно ли вам это, значит, оно вам не нужно.
Если у вас много данных, которые перемещаются туда-сюда, а состояния компонента React верхнего уровня недостаточно распространять его. Время пришло…
Redux позволяет вынести состояние источника правды из компонента верхнего уровня в отдельный объект. И единственный способ изменить текущее состояние — это взаимодействовать с этим объектом. Этот объект называется Магазин.
неизменность
Понимание неизменности очень важно для работы с Redux. Потому что в Redux состояние неизменяемо 🌳.
Идея неизменяемых данных проста, вы не можете их изменить. Как натуральные числа. 2
— натуральное число, и что бы вы ни делали, оно не изменит 2
. Вы можете оперировать им и, скажем, добавить к нему 3
, но результатом будет другое натуральное число, 5
. 5
— еще одно натуральное число.
Чем хороши неизменяемые данные? Потому что вы можете передавать его и не беспокоиться, что он будет изменен так, как вы этого не ожидаете. Это становится еще удобнее в распределенной многопоточной среде, но это уже другой разговор.
Неизменяемый по соглашению
Неизменяемые данные — ключевой аспект любой архитектуры на основе Redux. Несмотря на то, что это ключевой аспект, реального принуждения нет, это так называемая неизменяемость по соглашению. Я думаю, что неизменяемость по соглашению — это не вещь… Если объект может быть изменен, он будет видоизменен, это всего лишь вопрос времени… Я настоятельно рекомендую отказаться от неизменности по соглашению, как только отслеживание состояния данных станет неудобным.
JavaScript имеет некоторые структуры данных, предоставляемые из коробки. Есть Object.freeze() и const, которые позволяют вам иметь некоторую неизменность. Тем не менее, их использование не очень эффективно с точки зрения памяти, потому что каждая операция потребует от вас копирования данных из одного места в другое. Довольно дорого, учитывая тот факт, что каждая копия потребует дополнительного выделения памяти, копирования и сборки мусора.
Чтобы избежать ошибок, нам понадобится что-то, что будет обеспечивать неизменность и эффективно управлять памятью. immutable.js делает именно это. Это библиотека с набором неизменяемых структур данных. Immutable JS использует постоянные векторы для вставки, слияния и т. д. Это устраняет необходимость в копировании и кэшировании данных.
Чистые функции
Математические функции
Неизменяемые данные — ключевой аспект дизайна Redux, и мы должны уважать его, независимо от того, используем ли мы его обычным способом или принудительно.
Но как обращаться с неизменяемыми данными так, чтобы мы могли извлекать из них пользу?
Вернемся к примеру с натуральными числами. Мы договорились, что натуральные числа неизменяемы, и попытались добавить 2
и 3
, которые в результате 5
. Это можно записать как 2 + 3 = 5
. Чтобы сделать его более общим, мы можем описать его как математическую функцию, вот так f(a, b) = a + b
. Это предсказуемо, не вызывает никаких побочных эффектов, для 2
и 3
всегда будет возвращаться 5
.
Чистые функции — это математические функции. И чистые функции очень хорошо работают с неизменяемыми данными, есть даже целая парадигма программирования, которая берет эти две основы в качестве своей базовой платформы, вы можете знать ее как функциональное программирование.
Мы говорили о состоянии и его неизменной природе в Redux. Мы также рассказали о Store и о том, как он защищает состояние от любого несанкционированного воздействия. И, наконец, мы обнаружили, что чистые функции — это очень удобный способ работы с неизменяемыми данными, который позволяет сохранить прозрачность и предсказуемость.
Сокращение функций
Единственный способ, которым Redux Store позволяет управлять своим состоянием 🌳, — это действия. Это специальные объекты-инструкции, очень похожие на Команды в CQRS или События в Event Sourcing. Они определяют действие/операцию, предназначенную для применения к состоянию и несущую необходимую полезную нагрузку. Добавление элемента в корзину — это действие, в котором элемент, который вы хотите добавить, является полезной нагрузкой.
Redux использует специальный тип функций высокого порядка для обработки действий, редукционную функцию. Сокращающие функции не новая концепция в JavaScript, функция array.reduce(reducerCallback, initialValue) сводит массив к одному значению. Он использует специальный пользовательский обратный вызов редуктора, который выполняется рекурсивно. (accumulator, currentValue) => nextAccumulator
Точно так же Redux Store будет использовать _специальный определяемый пользователем _обратный вызов редюсера, который будет выполняться синхронно при отправке действия. Как вы могли догадаться, редьюсер должен быть чистой функцией. Он принимает состояние и действие и вычисляет следующее состояние.(state, action) => nextState
Перехват и подписка
Redux использует шаблон промежуточного программного обеспечения для предоставления точек интеграции до и после отправки действия. Вы можете складывать несколько функций промежуточного программного обеспечения. Вы отвечаете за то, продолжается ли цепочка выполнения или нет. Вы можете управлять им с помощью функции next(action)
.
Еще одна точка интеграции — Redux Listeners, расположенные ближе к обратному вызову редьюсера, чем к промежуточному ПО. Функции слушателя выполняются одна за другой сразу после редуктора. Слушатели не имеют никакого контроля над потоком выполнения.
Имейте в виду, это просто
Redux не принуждает вас, он дает вам возможность, предоставляя структуру. Вы можете иметь несколько хранилищ, изменять состояние, создавать побочные эффекты в ваших редуцирующих функциях и, наконец, вам вообще не нужно использовать его в качестве источника истины состояния.
Redux — это не ракетостроение 🚀, это просто реализация паттерна Flux, и если убрать из createStore.ts все проверки ошибок, точки расширения комментариев, то он уместится в 20–30 строк JavaScript-кода.
function createStore(reducer) {
var state;
var listeners = \[\]
function getState() {
return state
}
function subscribe(listener) {
listeners.push(listener)
return function unsubscribe() {
var index = listeners.indexOf(listener)
listeners.splice(index, 1)
}
}
function dispatch(action) {
state = reducer(state, action)
listeners.forEach(listener => listener())
}
dispatch({})
return { dispatch, subscribe, getState }
}
В Redux происходит еще несколько вещей, и он немного отличается от Flux, однако мы не будем слишком углубляться. Мы рассмотрели достаточно, чтобы перейти к следующей главе и разработать вариант использования Redux на серверной части.
Первоначально опубликовано на https://valerii-udodov.com 29 марта 2020 г.