Это первая статья из серии, в которой мы попытаемся выяснить, есть ли место для 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 г.