Что такое Магазин?

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

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

Повышая предсказуемость своего состояния, вы уменьшаете количество ошибок в своем приложении.

Вероятно, к вашему удивлению, это не курс по Redux, это не курс по React, это курс о том, как сделать ваше состояние более предсказуемым. Паттерны и ментальные модели, которые вы изучите в этом курсе, выходят за рамки какой-либо конкретной библиотеки интерфейсов.

Итак, если целью этого курса является повышение предсказуемости вашего состояния, как нам это сделать? Если вы вспомните любое обычное приложение, которое вы писали ранее, велика вероятность того, что состояние было разбросано по всему вашему приложению. Это плохо? Конечно, нет. Однако это не делает наше состояние более предсказуемым. Вероятно, это делает его менее предсказуемым.

Вместо того, чтобы разбрасывать состояние по всему приложению, первый принцип, который мы изучим, заключается в размещении всего нашего состояния в одном месте. Мы назовем это место «деревом состояний». Имея наше состояние в одном месте, мы можем определить четкие правила доступа к этому состоянию и его обновления.

Хватит махать руками, какие практические преимущества даст нам наше дерево состояний?

Во-первых, общий кеш. У вас когда-нибудь было два компонента внутри приложения React, которым нужно было получить доступ к одной и той же части состояния? Обычно вас учат «перемещать состояние вверх» к ближайшему родительскому компоненту. Это работает, пока не работает. Имея наше состояние в одном дереве состояний, любые компоненты, которым нужен доступ к нему, могут получить доступ — независимо от их местоположения в приложении.

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

Следующим преимуществом является улучшенный инструментарий разработчика. Поскольку все наше состояние находится в одном месте, другие части нашего приложения могут быть перезагружены без потери нашего состояния. Наряду с этим также легче создавать инструменты вокруг вопроса «как государство сюда попало?».

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

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

Теперь, когда мы установили, что размещение нашего состояния в одном месте, вероятно, является хорошей идеей, нам также нужен способ взаимодействия с ним. В частности, нам нужен способ получить наше состояние, обновить наше состояние и прослушать изменения в нашем состоянии. Итак, у нас есть имя, назовем его «Магазин».

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

Представление преобразований состояния в виде действий

На этом этапе для повышения предсказуемости мы хотим создать набор всех возможных событий, которые могут произойти в нашем приложении, которые будут обновлять состояние. Мы будем использовать простые старые объекты JavaScript для представления этих событий, но переименуем их в «действия».

Действия — это полезные данные, которые отправляют инструкции о том, какой тип преобразования следует выполнить для состояния вашего приложения, а также любую другую соответствующую информацию. Это причудливый способ сказать: «действие — это объект, который описывает, какое преобразование вы хотите произвести в своем состоянии». Объект представляет собой идеальную структуру данных для действия, поскольку он может идеально описать тип события, произошедшего в вашем приложении.

Давайте посмотрим, как может выглядеть типичное действие

const action = {
  type: "LIKE_TWEET",
  id: 999417610456724890,
  uid: "denislistiadi",
};

Что ж, это антиклиматически. Как упоминалось выше, действие — это просто объект, описывающий какое-то действие (или событие), которое может произойти в вашем приложении. Обратите внимание, что у действий должно быть свойство type, чтобы указать «тип» происходящего действия. Кроме type’ структура действия зависит от вас.

Состав редуктора

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

Допустим, мы имели дело с деревом состояний, которое имело такую ​​форму.

{
  users: {},
  setting: {},
  tweets: {
    hfolmg: {
      id: 'hfolmg',
      text: 'What is a javascript?',
      author: {
        name: 'Denis Listiadi',
        id: 'denislistiadi',
        avatar: 'twt.com/dl.jpg'
      }
    }
  }
}

В нашем дереве состояний есть три основных свойства: users, settings и tweets. Естественно, мы создадим отдельный редьюсер для каждого из них, а затем создадим один корневой редюсер, используя метод Redux combineReducers.

const reducer = combineReducers({
  users,
  settings,
  tweets,
});

combineReducers под капотом - это наш первый взгляд на состав редуктора. combineReducers отвечает за вызов всех остальных редюсеров, передавая им ту часть состояния, которая им нужна. Мы делаем один корневой редьюсер, составляя кучу других редюсеров вместе. Имея это в виду, давайте подробнее рассмотрим наш редьюсер tweets и то, как мы можем снова использовать композицию редюсера, чтобы сделать его более разделенным. В частности, давайте посмотрим, как пользователь может изменить свой аватар в соответствии со структурой нашего магазина в настоящее время.

Вот скелет, с которого мы начнем

function tweets (state = {}, action) {
  switch(action.type){
    case ADD_TWEET :
      ...
    case REMOVE_TWEET :
      ...
    case UPDATE_AVATAR :
      ???
  }
}

Нас интересует последний, UPDATE_AVATAR. Это интересно, потому что у нас есть некоторые вложенные данные — и помните, редукторы должны быть чистыми и не могут изменять какое-либо состояние.

Вот один подход.

function tweets (state = {}, action) {
  switch(action.type) {
    case ADD_TWEET :
      ...
    case REMOVE_TWEET :
      ...
    case UPDATE_AVATAR :
      return {
        ...state,
        [action.tweetId]: {
          ...state[action.tweetId],
          author: {
            ...state[action.tweetId].author,
            avatar: action.newAvatar
          }
        }
      }
  }
}

Это много операторов спреда. Причина этого в том, что для каждого слоя мы хотим распространить все свойства этого слоя на новые объекты, которые мы создаем (из-за неизменяемости). Что если, подобно тому, как мы разделили редюсеры tweets, users и settings, передав им срез дерева состояний, который их интересует, мы проделаем то же самое для нашего редуктора tweets и его вложенных данных?

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

Все, что мы сделали, — это разделили каждый слой наших вложенных tweets данных на отдельные редюсеры. Затем, как и в случае с нашим корневым редуктором, мы передаем этим редукторам часть состояния, которое им нужно.

ПО промежуточного слоя Redux

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

Вот несколько популярных пакетов в экосистеме Redux, реализованных через промежуточное ПО.

  • redux-api-middleware: промежуточное ПО Redux для вызова API.
  • redux-logger: промежуточное ПО Logger для Redux.
  • redux-promise-middleware: промежуточное ПО Redux для разрешения и отклонения промисов с условными оптимистичными обновлениями.
  • redux-thunk: промежуточное ПО Thunk для Redux.
  • redux-logic: промежуточное ПО Redux для организации бизнес-логики и побочных эффектов действий.
  • redux-observable: промежуточное ПО RxJS для побочных эффектов действий в Redux с использованием Epics.
  • redux-test-recorder: промежуточное ПО Redux для автоматического создания тестов для редукторов посредством взаимодействия с пользовательским интерфейсом.
  • redux-reporter: отчет о действиях и метаданных сторонним поставщикам, чрезвычайно полезный для аналитики и обработки ошибок (New Relic, Sentry, Adobe DTM, Keen и т. д.)
  • redux-localstorage: усилитель хранилища, который синхронизирует (подмножество) состояние вашего хранилища Redux с localStorage.
  • redux-node-logger: регистратор Redux для узловых сред
  • redux-catch: промежуточное ПО для перехвата ошибок
  • redux-cookies-middleware: промежуточное ПО Redux, которое синхронизирует подмножество состояния вашего магазина Redux с файлами cookie.
  • redux-test-recorder: регистратор тестов Redux — это промежуточное программное обеспечение с избыточностью + включает компонент для автоматического создания тестов для ваших редукторов на основе действий в вашем приложении.