Middleware · Redux documentation in russian
Ви бачили мідлвари у дії у прикладі асинхронних дій. Якщо ви коли-небудь використовували такі серверні бібліотеки як Express та Koa, то, ймовірно, ви вже добре знайомі з концепцією мідлвар. У цих фреймворках мідлвари - це частини коду, які ви можете помістити між фреймворком, що приймає запит, і фреймворком, що генерує відповідь. Наприклад, мідлвари з Express або Koa можуть додавати CORS-заголовки, логування, стиснення і т.д. Найкраща особливість мідлварів полягає в тому, що їх можна поєднувати в ланцюжки/послідовності. Ви можете використовувати безліч незалежних сторонніх мідлварів в одному проекті.
Redux-мідлвари, на відміну від мідлварів Express або Koa, вирішують трохи інші проблеми, але концептуально схожим способом.Вони надають сторонню точку розширення між відправкою дії та моментом, коли ця дія досягає редюсера. Люди використовують Redux-мідлвари для логування, повідомлення про помилки, спілкування з асинхронним API, роутингу і т.д.
Розуміння мідлварів
Т.к. Мідлвари можуть використовуватися для різних завдань, у тому числі і для асинхронних звернень до API, дуже важливо, щоб ви розуміли, звідки вони прийшли. Ми покажемо вам хід думок, крок за кроком, що веде до мідлварів, використовуючи логування та повідомлення про помилки як приклад.
Проблема: логування
Одна з переваг Redux — він робить зміни стану програми передбачуваними та прозорими. Щоразу, коли надсилається дія, обчислюється і зберігається новий стан. Стан не може змінитися самостійно, він може змінюватись лише як послідовність певних дій.
Як ми підходимо до цього з Redux?
Спроба #1: Логуємо вручну
Найпростіше рішення – самостійнозаписувати дію та стан щоразу, коли ви викликаєте store.dispatch(action) . Насправді, це не надто гарне рішення, це просто перший крок на шляху до розуміння проблеми.
Зверніть увагу
Якщо ви використовуєте react-redux або схожий біндинг, ви, ймовірно, хочете мати безпосередній доступ до екземпляра стану у ваших компонентах. Для наступних кількох параграфів уявіть, що ви передаєте стан явно.
Наприклад, ви викликаєте такий код, коли створюєте todo-елемент:
Для того щоб логувати дію та стан, ви можете змінити код приблизно так:
Це дасть бажаний ефект, але ви не хотіли б робити так щоразу.
Спроба #2: Обертаємо Dispatch
Ви можете винести логування у функцію:
Ви можете використовувати її скрізь замість звичайного store.dispatch() :
Ми могли б закінчити на цьому, але не дуже зручно імпортувати спеціальну функцію щоразу.
Спроба #3: Monkeypatching для Dispatch
Це вже ближче до того, що нам потрібне! Не важливо, звідки ми посилаємо дію, її гарантовано буде залоговано. Monkeypatching ніколи не здасться правильним ходом, але поки що ми можемо з цим жити.
Проблема: Повідомлення про помилки.
Що, якщо ми захочемо застосуватибільше одного такого перетворення на dispatch?
Хіба не було б корисно, якби кожного разу, коли помилка викидалася як результат відправлення будь-якої дії, ми могли б відправити її (помилку), разом зі стеком викликів, дією, яка викликала помилку та актуальним станом у сервісі повідомлення про помилки, такий як Sentry. У такому разі набагато легше відтворити помилку у розробці.
Однак важливо, щоб ми тримали логування та повідомлення про помилки окремо. УВ ідеальному випадку ми хочемо отримати їх як різні модулі з різних пакетів. В іншому випадку ми не зможемо мати екосистему з такого роду утиліт. (Підказка: ми повільно підходимо до того, що таке мідлвари!)
Якщо логування та повідомлення про помилки є окремими утилітами, то вони можуть виглядати так:
Але це ще не дуже добре.
Спроба #4: Ховаємо Monkeypatching
Monkeypatching – це хак. "Замініть будь-який метод, який хочете" - що це за вид API? Давайте розберемося у його суті. Раніше наші функції замінювали store.dispatch. Що якби вони натомість повертали нову функцію dispatch?
Ми могли б надати функцію-помічник усередині Redux, яка могла б застосовувати актуальний monkeypatching як частину імплементації:
Ми можемо використовувати такий підхід для застосування кількох мідлварів:
Але це досі monkeypatching. Факт того, що ми ховаємо його всередині бібліотеки, не скасовує використання monkeypatching.
Спроба #5: Забираємо Monkeypatching
Навіщо ми перезаписуємо dispatch? Звичайно ж, для того, щоб мати можливість потім його викликати. Але є ще й інша причина: кожен мідлвар має доступ (і можливість викликати) раніше обернутий store.dispatch :
Це важливо для можливості об'єднувати мідлвари в ланцюжки!
Якщо applyMiddlewareByMonkeypatching не збереже store.dispatch відразу після обробки першого мідлвара, store.dispatch буде продовжувати посилатися на оригінальну функцію dispatch. Отже другий мідлвар теж буде пов'язаний з оригінальною функцією dispatch.
Це той момент, коли “we need to go deeper”, так що має сенс витратити деякий час на це. Каскад функцій виглядає страшним. Стрілецькі функції з ES6 роблять це карирування трохи більшепростим для очей:
Саме так виглядають мідлвари в Redux.
Тепер мідлвар приймає функцію відправки дії next() і повертає іншу функцію відправки дії, яка, своєю чергою, є функцією відправки дії next() для мідлвару зліва. Все ще корисно мати доступ до деяких методів сховища, наприклад до getState() , отже, store залишається доступним як аргумент найвищого рівня.
Спроба #6: Найпростіше застосування мідлварів
Замість applyMiddlewareByMonkeypatching() ми могли б написати функцію applyMiddleware() , яка спочатку отримує фінальну, повністю обернуту функцію dispatch() і повертає копію сховища, яка використовує цю функцію:
Реалізація applyMiddleware() , яка поставляється з Redux, схожа на цю, але відрізняється трьома важливими аспектами:
Вона надає мідлвар підмножина API сховища: методи dispatch(action) і getState() .
Вона використовує деякі хитрощі для того, щоб переконатися, що дія знову пройде через весь ланцюжок мідлварів, включаючи поточний, якщо ви викликаєте store.dispatch(action) з мідлвара замість next(action) . Це корисно для асинхронних мідлварів, як ми бачили раніше.
Щоб гарантувати, що ви можете застосувати мідлвар тільки один раз, вона працює з createStore() , а не з самим store . Замість (store, middlewares) => store, її сигнатурою є (. middlewares) => (createStore) => createStore.
Фінальний підхід
Даний мідлвар який ми щойно написали:
Ось так можна його застосувати до Redux сховища:
От і все! Тепер будь-яка дія, відправлена в екземпляр сховища буде проходити через logger та crashReporter :
Сім прикладів
Якщо ваша головаскипіла від прочитання попереднього розділу, уявіть, як було це написати. Цей розділ призначений для розслаблення мене і вас та допоможе запустити ваші шестерні.
Кожна з функцій, наведених нижче, є валідним Redux-мідлваром. Вони не є однаково корисними, але, принаймні, вони однаково кумедні.