Вступ до функціонального реактивного програмування в Redux

Почнемо з того, що отримаємо основне уявлення про те, що таке «реактивне програмування»:

Реактивне програмування - це асинхронна парадигма програмування, що стосується потоків даних та поширення змін.

- Вікіпедія

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

Rx базується на спостережуваних , спостерігачах та операторах

По суті, спостерігач підписується на спостережуваний.

Потім Observable видає потоки даних, які спостерігач прослуховує і реагує на них, запускаючи ланцюг операцій над потоком даних. Реальна потужність надходить від Операторів або “Реактивних розширень” (звідси термін Rx) .

Оператори дозволяють трансформувати, комбінувати, маніпулювати та працювати з послідовностями елементів, що випускаються Observables.

Якщо ви не знайомі з Rx, вам може бути важко зрозуміти та використовувати Redux-Observable. Тож пропоную спочатку забруднити руки Rx!

Тепер використовуємо RxJS з Redux.

Redux-спостережуваний

Redux-Observable - це проміжне програмне забезпечення для Redux на основі RxJS

Ось що Redux Docs мають сказати про проміжне програмне забезпечення в Redux:

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

Проміжне програмне забезпечення Redux можна використовувати для ведення журналу, звіт про аварійне завершення роботи, спілкування з асинхронним API, маршрутизації тощо. Або можна сказати побічні ефекти взагалі.

То як Redux-Observable робить все це?

Через епос. Епоси є основним примітивом Redux-Observable. Епопея - це просто проста функція, яка виконує дію, а потім повертає іншу дію. Action In → Action Out . Тому дії розглядаються як потоки.

Кожна дія, надіслана в будь-якому компоненті React, пройде через такі функції (Epics), як потік.

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

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

$Після того, як actionвикористовується для вказівки того, що ці змінні посилаються потоки. Отже, ми маємо потік дій, що передаються в filterЕпопею, на якій ми використовували оператор RxJS.

Цей оператор фільтра відфільтровує всі дії, які не належать до typePING! Тому Epic pingEpicзаймається лише обробкою дій type‘PING’. Нарешті, це action‘PING’зіставляється з новим actionіз type‘PONG’задовільних основних правил Epics: Action In → Action Out .

Оскільки кожна епопея стосується лише певного типу дії, ми маємо спеціальний оператор для action$(stream), щоб відфільтрувати небажані дії з потоку. Цей оператор є 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) =>

Ми можемо просто ca ll 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(`//someapi/words/${action.payload}`) .map(payload => ({ type: 'SET_RESULTS', payload })) .catch(payload => Observable.of({type: 'API_ERROR', payload})) )

Забагато, щоб впоратися ?! Не хвилюйся, давайте розберемо це.

Епопея отримує потік всіх дій oftype‘SEARCH’. Оскільки користувач постійно набирає текст, корисне навантаження кожної вхідної дії ( action.payload) містить оновлений рядок пошуку.

The operator debounceTime() is used to filter out some of the actions in the stream except the last one. It basically passes an action through it only if 1 second has elapsed without it receiving another action or observable.

We then make the AJAX request, mapping the results to another action 'set_RESULTS' which takes the response data (payload) to the reducer, which is the Action Out part.

Any API errors are caught using the catch operator. A new action is emitted with the error details and later displays a toaster with the error message.

Notice how the catch is inside the mergeMap() and after the AJAX request? This is because the mergeMap() creates a chain that is isolated. Otherwise the error would reach ofType() and will terminate our Epic. If that happens, the Epic will stop listening to any action in the future!

We can use traditional promises for AJAX requests as well. However, they have this inherent problem of not being able to get cancelled. So another important use case for using Epics is AJAX cancellation.

We use the takeUntil operator to handle this issue. This is done just like we used that catch operator inside mergeMap and after the AJAX request.

This is because takeUntil must stop the current AJAX request and not the entire Epic! Therefore, isolating operator chains is important here as well.

Debouncing, throttling, filtering, AJAX cancellation and others, are just the tip of the iceberg. We have a myriad of operators at our disposal, making difficult use-cases trivial to solve. Using these operators, you can get as creative as your imagination allows you to be! Functional Reactive Programming (FRP) is elegant in its own way.

My focus for this article was on the explanation part of FRP in Redux using Redux-Observable. For setting up Redux-Observable in React+Redux, refer to the official docs — its very well documented, detailed, and easy-breezy.

Be sure to check out my other article on Redux which explores the best practice for creating reducers:

Reducing the Reducer Boilerplate With createReducer()

First, a quick recap of what reducers in Redux are:medium.freecodecamp.org

Want to improve your JavaScript basics? Give these a read:

JavaScript ES6 Functions: The Good Parts

ES6 offers some cool new functional features that make programming in JavaScript much more flexible. Let’s talk about…medium.freecodecamp.orgA guide to JavaScript variable hoisting ? with let and const

New JavaScript developers often have a hard time understanding the unique behaviour of variable/function hoisting.medium.freecodecamp.org Function Hoisting & Hoisting Interview Questions

This is a part 2 for my previous article on Variable Hoisting titled “A guide to JavaScript variable hoisting ? with…medium.freecodecamp.org

Peace ✌️