Давайте начнем с понимания того, что такое «реактивное программирование»:

Реактивное программирование - это асинхронная парадигма программирования, связанная с потоками данных и распространением изменений.
- Википедия

ReactiveX или Rx - самый популярный API для реактивного программирования. Он построен на идеологиях наблюдаемого шаблона, шаблона итератора и функционального программирования. В Rx есть библиотеки для разных языков, но мы будем использовать RxJS.

Rx основан на Observables, Observers и Operators.

По сути, Observer подписывается на Observable.

Затем Observable испускает потоки данных, которые Observer прослушивает и реагирует, запуская цепочку операций с потоком данных. Реальная мощность исходит от операторов или «реактивных расширений» (отсюда и термин Rx) .

Операторы позволяют вам преобразовывать, комбинировать, манипулировать и работать с последовательностями элементов, генерируемых Observables.

Если вы не знакомы с Rx, вам может быть трудно понять и использовать Redux-Observable. Так что я предлагаю вам сначала испачкать руки Rx!

Теперь перейдем к использованию RxJS с Redux.

Redux-Observable

Redux-Observable - это промежуточное ПО на основе RxJS для Redux

Вот что Redux Docs говорит о промежуточном программном обеспечении в Redux:

Промежуточное ПО обеспечивает стороннюю точку расширения между отправкой действия и моментом его достижения редуктором.

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

Так как же Redux-Observable все это делает?

Через Эпосы. Эпики - это основной примитив Redux-Observable. Эпопея - это простая функция, которая выполняет действие, а затем возвращает другое действие. Действие в → Действие нет. Таким образом, действия рассматриваются как потоки.

Каждое действие, отправленное в любом компоненте React, будет проходить через такие функции (эпики) как поток.

Давайте посмотрим, как выглядит простой эпос, который принимает action 'PING’ и возвращает новое action 'PONG’:

const pingEpic = action$ =>
  action$.filter(action => action.type === 'PING')
    .mapTo({ type: 'PONG' })

$ после action используется, чтобы указать, что эти переменные ссылаются на потоки. Итак, у нас есть поток действий, передаваемый в Epic, в котором мы использовали оператор filter RxJS.

Этот оператор фильтра отфильтровывает все действия, не относящиеся к type PING! Следовательно, Epic pingEpic занимается только обработкой действий type ‘PING’. Наконец, этот action ‘PING’ отображается на новый action из type ‘PONG’, удовлетворяющий основному правилу Эпиков: Действие в → Действие завершено.

Поскольку каждый эпос касается только определенного типа действий, у нас есть специальный оператор для action$ (поток), чтобы отфильтровать нежелательные действия из потока. Этот оператор является оператором ofType().

Переписывая предыдущий эпос с использованием ofType, получаем:

const pingEpic = action$ =>
  action$.ofType('PING')
  .mapTo({ type: 'PONG' })

Если вы хотите, чтобы ваш эпос допускал более одного типа действий, оператор ofType() может принимать любое количество аргументов, например: ofType(type1, type2, type3,...).

Знакомство с особенностями работы эпиков

Вы можете подумать, что действие «PING» просто приходит и поглощается этой эпопеей. Это не относится к делу. Всегда нужно помнить две вещи:

  1. Каждое действие всегда сначала выполняется редуктором
  2. Только после этого действие получило эпос

Следовательно, цикл Redux работает нормально, как и должен.

action ‘PING’ сначала достигает редуктора, а затем его получает Epic, затем изменяется на новый action ‘PONG’, который отправляется редуктору.

Мы даже можем получить доступ к состоянию магазина внутри Epic, потому что второй аргумент Epic - это облегченная версия Redux Store! См. Ниже:
const myEpic = (action$, store) =>
Мы можем просто вызвать store.getState() и получить доступ к состоянию внутри Epics.

Сцепление операторов

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

Эти операторы Rx позволяют объединять асинхронные последовательности декларативным образом со всеми преимуществами эффективности обратных вызовов, но без недостатков вложенных обработчиков обратных вызовов, которые обычно связаны с асинхронными системами.

Мы получаем преимущества обратных вызовов без этого пресловутого «ада обратных вызовов».

Посмотрите, как мы можем использовать возможности операторов ниже.

Типичный вариант использования

Предположим, что мы хотим найти слово с помощью чего-то вроде словарного API, используя текст, введенный пользователем в режиме реального времени. В основном мы занимаемся хранением (в магазине Redux) и отображением результатов вызова API. Мы также хотели бы отклонить вызов API, чтобы API вызывался, скажем, в течение 1 секунды после того, как пользователь прекращает печатать.

Вот как это будет сделано с помощью операторов Epic и RxJS:

const search = (action$, store) =>
  action$.ofType('SEARCH')
  .debounceTime(1000)
  .mergeMap(action =>
    ajax.getJSON(`https://someapi/words/${action.payload}`)
     .map(payload => ({ type: 'SET_RESULTS', payload }))
     .catch(payload => Observable.of({type: 'API_ERROR', payload}))
  )

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

Эпопея получает поток действий все oftype ‘SEARCH’. Поскольку пользователь постоянно набирает текст, полезная нагрузка каждого входящего действия (action.payload) содержит обновленную строку поиска.

Оператор debounceTime() используется для фильтрации некоторых действий в потоке, кроме последнего. Он в основном передает действие через него, только если истекла 1 секунда, но он не получил другого действия или наблюдаемого.

Затем мы выполняем запрос AJAX, сопоставляя результаты с другим действием 'set_RESULTS', которое принимает данные ответа (payload) редуктору, который является частью Action Out.

Любые ошибки API выявляются с помощью оператора catch. Создается новое действие с подробными сведениями об ошибке, а затем отображается тостер с сообщением об ошибке.

Обратите внимание, как ловушка находится внутри mergeMap() и после запроса AJAX? Это потому, что mergeMap() создает изолированную цепочку. В противном случае ошибка достигнет ofType() и остановит наш Epic. Если это произойдет, Epic перестанет прислушиваться к любым действиям в будущем!

Мы также можем использовать традиционные обещания для запросов AJAX. Однако у них есть неотъемлемая проблема невозможности быть отмененной. Итак, еще один важный вариант использования Epics - это отмена AJAX.

Мы используем оператор takeUntil для решения этой проблемы. Это делается так же, как мы использовали этот оператор catch внутри mergeMap и после запроса AJAX.

Это потому, что takeUntil должен останавливать текущий запрос AJAX, а не весь Epic! Следовательно, здесь также важна изоляция цепочек операторов.

Ослабление, регулирование, фильтрация, отмена AJAX и другие - это лишь верхушка айсберга. В нашем распоряжении множество операторов, что упрощает решение сложных сценариев использования. Используя эти операторы, вы можете проявить столько творчества, насколько позволяет ваше воображение! Функциональное реактивное программирование (FRP) по-своему элегантно.

В этой статье я сосредоточился на части объяснения FRP в Redux с использованием Redux-Observable. Для настройки Redux-Observable в React + Redux обратитесь к официальной документации - она ​​очень хорошо документирована, подробна и проста.

Обязательно ознакомьтесь с другой моей статьей о Redux, в которой исследуются лучшие практики создания редукторов:



Хотите улучшить свои основы JavaScript? Прочтите это:







Мир ✌️