Eine Einführung in die funktionale reaktive Programmierung in Redux

Beginnen wir mit einer Grundidee von „Reactive Programming“:

Reaktive Programmierung ist ein asynchrones Programmierparadigma, das sich mit Datenströmen und der Ausbreitung von Veränderungen befasst.

- Wikipedia

ReactiveX oder Rx ist die beliebteste API für die reaktive Programmierung. Es basiert auf den Ideologien des beobachtbaren Musters, des Iteratormusters und der funktionalen Programmierung. Rx hat Bibliotheken für verschiedene Sprachen, aber wir werden RxJS verwenden.

Rx basiert auf Observablen , Beobachtern und Operatoren

Ein Beobachter abonniert im Wesentlichen ein Observable.

Das Observable sendet dann Datenströme aus, die der Beobachter abhört und auf die er reagiert, und setzt eine Kette von Operationen im Datenstrom in Gang. Die Wirkleistung kommt von Operatoren oder „Reactive Extensions“ (daher der Begriff Rx) .

Mit Operatoren können Sie die Sequenzen von Elementen, die von Observables ausgegeben werden, transformieren, kombinieren, bearbeiten und damit arbeiten.

Wenn Sie mit Rx nicht vertraut sind, fällt es Ihnen möglicherweise schwer, Redux-Observable zu verstehen und zu verwenden. Also schlage ich vor, dass Sie sich zuerst mit Rx die Hände schmutzig machen!

Nun zur Verwendung von RxJS mit Redux.

Redux-beobachtbar

Redux-Observable ist eine RxJS-basierte Middleware für Redux

Dies ist, was Redux Docs über Middleware in Redux zu sagen hat:

Middleware bietet einen Erweiterungspunkt eines Drittanbieters zwischen dem Versenden einer Aktion und dem Moment, in dem sie den Reduzierer erreicht.

Redux Middleware kann für die Protokollierung, Absturzberichterstattung, Kommunikation mit einer asynchronen API, Routing und mehr verwendet werden. Oder wir können allgemein Nebenwirkungen sagen .

Wie macht Redux-Observable das alles?

Durch Epen. Epen sind das Kernprimitiv von Redux-Observable. Ein Epos ist nur eine einfache Funktion, die eine Aktion ausführt und dann eine andere Aktion zurückgibt. Aktion ein → Aktion aus . Aktionen werden daher als Streams behandelt.

Jede Aktion, die in einer Komponente von React ausgelöst wird, durchläuft solche Funktionen (Epics) als Stream.

Mal sehen, wie ein einfaches Epos aussieht, das action'PING’ein neues aufnimmt und zurückgibt action'PONG’:

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

Das $Nachher actionwird verwendet, um anzuzeigen, dass diese Variablen auf Streams verweisen. Wir haben also eine Reihe von Aktionen, die an das Epic übergeben werden, für das wir den filterOperator von RxJS verwendet haben.

Dieser Filteroperator filtert alle Aktionen heraus, die nicht von typePING! Daher befasst sich das Epos pingEpicnur mit der Handhabung von Aktionen der type‘PING’. Schließlich wird diese action‘PING’auf einen neuen kartiert actionder type‘PONG’Erfüllung der Hauptregel Epics: Aktion In → Aktion Out .

Da sich jedes Epos nur mit einer bestimmten Art von Aktion befasst, haben wir einen speziellen Operator für action$(Stream), um unerwünschte Aktionen aus dem Stream herauszufiltern. Dieser Operator ist der ofType()Operator.

Wenn ofTypewir das vorherige Epos mit umschreiben, erhalten wir:

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

Wenn Sie möchten, dass Ihr Epos mehr als eine Art von Aktion zulässt, kann der ofType()Operator eine beliebige Anzahl von Argumenten wie folgt verwenden : ofType(type1, type2, type3,...).

Einstieg in die Besonderheiten der Funktionsweise von Epics

Sie können denken, dass die Aktion 'PING' einfach hereinkommt und von diesem Epos verzehrt wird. Das ist nicht der Fall. Es gibt zwei Dinge, an die man sich immer erinnern sollte:

  1. Jede Aktion geht immer zuerst zum Reduzierer
  2. Erst danach erhält das Epos diese Aktion

Daher funktioniert der Redux-Zyklus normal wie er sollte.

Der action‘PING’erreicht zuerst den Reduzierer und wird dann vom Epic empfangen und dann in einen neuen geändert, action‘PONG’der an den Reduzierer gesendet wird.

Wir können sogar innerhalb eines Epos auf den Status des Geschäfts zugreifen, da das zweite Argument eines Epos eine leichte Version des Redux-Geschäfts ist! Siehe unten:

const myEpic = (action$, store) =>

Wir können einfach ca ll store.getStat() und auf den Status in Epics zugreifen.

Verkettung des Bedieners

Zwischen dem Empfang einer Aktion und dem Versenden einer neuen Aktion können wir alle Arten von asynchronen Nebenwirkungen ausführen, die wir möchten, z. B. AJAX-Aufrufe, Web-Sockets, Timer usw. Dies erfolgt mit den zahlreichen von Rx bereitgestellten Operatoren .

Mit diesen Rx-Operatoren können Sie asynchrone Sequenzen deklarativ zusammenstellen, mit allen Effizienzvorteilen von Rückrufen, jedoch ohne die Nachteile von verschachtelten Rückrufhandlern, die normalerweise mit asynchronen Systemen verbunden sind.

Wir erhalten die Vorteile von Rückrufen ohne diese berüchtigte "Rückrufhölle".

Sehen Sie unten, wie wir die Leistung der Betreiber nutzen können.

Ein häufiger Anwendungsfall

Assume that we want to search for a word with something like a dictionary API using text entered by the user in real-time. We’re basically dealing with storing (in the Redux store) and displaying the results from the API call. We would also like to debounce the API call so that the API is called within, say, 1 second of when the user stops typing.

This is how it’ll be done using Epic and RxJS operators:

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})) )

Too much to handle?! Don’t worry, let’s break that down.

The epic is getting a stream of actions all oftype‘SEARCH’. Since the user is continuously typing, the payload of every incoming action (action.payload) contains the updated search string.

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 ✌️