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

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

Настройка контекста

Во-первых, давайте поговорим о некоторых основных принципах, на которых мы будем строить. Я считаю следующие моменты наилучшей практикой:

  • Создатели действий возвращают ТОЛЬКО правильно оформленное действие. Никаких вычислений и побочных эффектов.
  • Действия должны представлять внешнее событие и называться соответствующим образом. Их не следует использовать в качестве механизма обмена сообщениями. Имена недопустимых действий: SET_ACTIVE_USER, CLOSE_MODAL,. Названия хороших действий: LOGIN_BUTTON_CLICKED, MODAL_CLOSE_REQUEST. Это помогает вам мыслить реактивно.
  • Бизнес-логика должна в основном находиться в редукторах или промежуточном программном обеспечении. Тупые компоненты легче тестировать и, как правило, с ними легче работать.
  • Компоненты должны быть полностью отделены от redux. Им не важно, откуда берется их реквизит. Это упрощает повторное использование и тестирование.
  • Контейнеры Redux должны быть полностью отделены от формы состояния. Это означает, что запрос или состояние поиска должны выполняться повторно используемыми функциями, селекторами AKA (проверьте повторный выбор и повторный выбор).
  • Следуя предыдущему пункту, не сохраняйте производное состояние в вашем магазине ... вместо этого используйте селекторы. Итак, вместо этого: { numberToAddA: 1, numberToAddB: 2, result: 3 }, сделайте следующее: { numberToAddA: 1, numberToAddB: 2 } и используйте селектор, чтобы получить результат в mapStateToProps.

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

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

Простое подтверждение концепции

store => next => async action => {
  switch(action.type) {
    case SEARCH_REQUEST:
      next(action)
      const response = await makeSearchRequest(action.query)
      store.dispatch(searchResponse(response.payload))
  }
}      

Приведенный выше фрагмент выполняет три действия, когда поиск был инициирован компонентом.

  1. Он передает действие дальше в цепочке промежуточного программного обеспечения / редукторах. Это необязательно ... возможно, вам не нужно, чтобы действие выполнялось через редукторы.
  2. Он отправляет сетевой запрос с данными о событии. В этом случае мы отправляем запрос в конечную точку поиска.
  3. Он отправляет новое действие при получении ответа. Вы также можете использовать next().

А как насчет Thunk?

Thunk - популярная библиотека, которая выполняет в основном то же самое (и даже больше) с помощью промежуточного программного обеспечения. Я предпочитаю настраиваемый шаблон промежуточного программного обеспечения, потому что здесь меньше неправильного направления, а Thunk нарушает первый принцип, перечисленный выше (создатели действий должны быть глупыми функциями, которые возвращают простые объекты). Еще одним преимуществом сетевого промежуточного программного обеспечения является то, что вся ваша сетевая логика (обработчики запросов / ответов и их вызовы) может находиться в одном месте, поэтому имитация, ведение журнала, отладка и обслуживание очень просты.

ПРИМЕЧАНИЕ. Я видел версию этого шаблона, в которой исходное действие украшается данными из ответа перед отправкой дальше. Не делай этого. Сохранение атомарности и непрозрачности действий означает, что вам не нужно использовать мозговые соки, рассуждая о своем коде.

Веселье с изменениями состояния

В то время как setState() React является асинхронным, dispatch() Redux - нет. А поскольку любое промежуточное ПО имеет ссылку на store, это означает, что мы можем получить доступ как к старому, так и к новому дереву состояний для данного действия.

Простое подтверждение концепции

store => next => action => {
  const oldState = store.getState()
  next(action)
  const newState = store.getState()
  // do something awesome with your newfound omnipotence
}

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

Зачем нам это нужно?

Предотвращение дорогостоящих операций для действий noop

if (newState !== oldState) {
  performExpensiveOperation()
}

Для идемпотентных действий мы можем захотеть предотвратить выполнение определенных операций при повторных отправках.

Проверка обязательного состояния

if (isTutorialMode(store.getState()) {
  tutorialModeHandler(action)
} else {
  next(action)
}

У вас могут быть определенные флаги в состоянии, которые радикально меняют способ обработки данного действия. В приведенном выше примере я почти уверен, что tutorialModeHandler порождает Clippy.

ПРИМЕЧАНИЕ. Это приводит к еще одному хорошему шаблону. Внешняя область промежуточного программного обеспечения должна содержать только код делегирования (переключатели и условные операторы), в то время как фактическая бизнес-логика может существовать в небольших тестируемых функциях обработчика.

Веб-сокеты

Мы уже видели, как промежуточное ПО может работать на вас в мире запросов / ответов. Также существует шаблон для взаимодействия с подключением через веб-сокет.

Простое подтверждение концепции

store => {
  // initialize connection
  const socket = new WebSocket()
  
  // set event listeners
  socket.on('messageReceived', (e) => {
    store.dispatch(messageReceived(e.data))
  })
  return next => action => {
    next(action)
    // emit events
    switch(action.type) {
      case MESSAGE_SUBMIT:
        socket.send(createMessageEvent(action))
    }
  }
}

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

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