Redux & React – Das Working Example

wer schon einmal größere Applikationen mit React programmiert hat weiß, dass man irgendwann an einem Punkt kommt, in dem man eine Art „globalen Storage“ braucht um seine Daten konsistent und überschaubar zu halten.

Die Antwort auf das Problem?
FLUX! bzw. REDUX.

Zumindest eine mögliche Antwort!

In diesem Artikel, lernst du die Grundprinzipien von Flux bzw. Redux und bekommst ein Working-Example mit dem du direkt Arbeiten kannst.

 

Ja aber was denn nun, Flux oder Redux?!?

Ganz einfach.

Flux ist eine Anwendungsarchitektur – Redux eine implementation dieser Architektur!

Facebook verwendet sie beispielsweise zum erstellen von Client-seitigen Webanwendungen.
Es ergänzt die View-Komponenten von React durch einen unidirektionalen Datenfluss. Dabei handelt es sich bei Flux eher um ein Muster als um einen formalen Rahmen.

 

Warum brauchen wir überhaupt Redux?

In React kann es bei der Zustandsübertragung („state transfer“) zwischen den Komponenten ziemlich chaotisch werden, da es bei wachsender Größe eines Projektes immer schwieriger wird den Überblick zu behalten.

Woher hat welche Komponente nun eigentlich ihre Daten bekommen? Wo genau ist denn der Callback nun definiert?

Dabei ist oftmals nicht nur das Problem, dass man nicht weiß woher die Daten kommen. Zusätzlich werden auch noch allerhand unnötige bzw. nicht gebrauchte Daten mit übertragen.

Nehmen wir folgende Komponenten-Hierarchie an:

Wenn wir nun Daten von der MediaLibrary Komponente an MediaFile übertragen wollen, müssen wir in React den Weg über die AvailableMedias Komponente gehen.

Ganz einfach, weil in React die Daten nur in eine Richtung fließen.

Redux löst genau dieses „State Transfer Problem“!

In Redux speichert man den gesamten Zustand (state) der Applikation im sogenannten Store!
Sauberer Code, einfacher Zustandstransfer und vor allem, einfacher Zugriff auf den Zustand der App sind die Hauptvorteile von Redux.

 

Ist Redux in ReactJS aufwändig zu implementieren?!?

Nein!

Knüpfen wir an unserem obigen Beispiel mit der MediaLibrary an.

Eine normale React Anwendung könnte in simpler Ausführung so aussehen:

Kurze Erklärung:

MediaLibrary – Sie ist die oberste Ebene.

Es ist sinnvoll den state immer so weit wie möglich oben zu verwalten. Daher ist diese Komponente in unserer Anwendung auch für den state verantwortlich.

AvailableMedias – Diese Komponente ist im Prinzip ein Container der später alle verfügbaren Medien unserer MediaLibrary anzeigt.

MediaFile – Diese Komponente ist einzig und allein dafür zuständig ein „Media File“ – in unserem Fall ein Bild – darzustellen.

Übrigens, AvailableMedias und MediaFile nennt man auch Präsentationskomponenten oder im englischen „Presentational Components“. Das sind Komponenten die properties auslesen und etwas Anzeigen.

Was passiert nun, wenn wir im state ebenfalls eine Liste mit allen Medien haben wollen, die ein Nutzer selektiert hat?

Idee: Wir erstellen ein Liste „selectedMedias“ in unserem state. Dann brauchen wir nur noch eine Funktion die sich darum kümmert, beim klick auf ein MediaFile „selectedMedias“ aktualisiert.

Das sehe dann etwa so aus:

Da unsere MediaLibrary den state verwaltet, haben wir auch hier den onClick handler implementiert. Nun fällt auch direkt auf, was ich oben bereits erwähnt habe.

MediaFile braucht nun den onClick handler, dazu müssen wir diesen aber auch an die Komponente AvailableMedias übergeben. Diese hat jedoch eigentlich absolut keine Verwendung dafür.

Nun könnte man meinen, ist jetzt ja nicht sooo wild! Wenn unsere Web-Applikation jedoch wächst, kann genau das ganz schön chaotisch werden!

Und genau da kommt Redux ins Spiel!

 

Kurze Einführung in Redux

Mithilfe von Redux können wir Anwendungen schreiben, die sich konsistent verhalten, in verschiedenen Umgebungen (Client, Server und nativ) laufen und einfach zu testen sind!

Redux verfolgt ein paar Grundprinzipien, beispielsweise:

  • Eine einzige Quelle der Wahrheit
  • Der state ist schreibgeschützt.
  • Änderungen werden mit reinen Funktionen vorgenommen.

Mit reinen oder auch „pure functions“ ist gemeint, dass man darin niemals Dinge implementiert wie:

  1. Mutation der Argumente.
  2. Funktionen mit Nebeneffekten, bspw. API-Aufrufe oder Routing-Transitionen.
  3. Aufruf von nicht-reinen Funktionen, bspw. Date.now() oder Math.random().

Wenn ich Redux in einigen Sätzen beschreiben müsste, wäre das meine grobe Beschreibung:

Der state der gesamten Anwendung wird in einem Objektbaum innerhalb eines einzelnen Store’s gespeichert. Die einzige Möglichkeit, den state zu ändern, besteht darin, eine sogenannte „action“ auszusenden. Das ist ein Objekt, das beschreibt, was passiert, nicht wie etwas passiert.
Um festzulegen, wie der Zustandsbaum durch „actions“ transformiert wird, implementieren wir sogenannte „Reducer„.

Das ist im Prinzip alles!

 

Hands-On: Implementation von Redux in React

Genug gesabbelt!
Fangen wir an unsere MediaLibrary umzubauen.

Actions in Redux

Zuerst definieren wir unsere sogenannte action die beschreibt, was passiert. Hierzu legen wir eine action.js an in der wir unsere erste Aktion definieren. Nämlich das selektieren von Medien.

Eine action gibt immer ein Objekt zurück, die zum einen den ‚type‘ der action enthält, sowie optionale Parameter. Beachte: Redux fügt automatisch für unser „media“ Objekt den key „media: media“ hinzu, wenn wir keinen eigenen definieren.

Reducer in Redux

Ein Reducer ist eine reine Funktion, die den vorherigen state und eine action entgegen nimmt und den nächsten state zurückgibt.

Wir mutieren den state nicht. Wir erstellen jedes mal eine Kopie des state’s mit Object.assign(), sonst würde die Komponente im schlimmsten Fall nicht updaten, wenn der state sich ändert, da es nach einem Update immer noch auf die gleiche Referenz des Objektes schaut.

Kurz erklärt:

Wir importieren die zuvor definierte action und erstellen ein „initialState“. Dieser sorgt dafür, dass der state beim ersten Aufruf vom Reducer mit den Initialwerten gesetzt wird.
Dank ES6 Notation können wir das direkt im Funktionsaufruf von media() erledigen.

Der Reducer macht nun nichts anderes als über die verschiedenen action’s zu switchen. Komisch formuliert, anders ist es mir nicht eingefallen!

Wichtig ist, dass „default:“ immer den state zurückgibt, da ein Reducer sich auf alle action’s registriert. Mit den switch filtern wir nur die action’s heraus, für die der Reducer sich interessiert.

Wie oben bereits erwähnt mutieren wir den state nicht, sondern geben ein neues state Objekt zurück! Wir könnten nun beliebig viele action’s definieren und den switch() in unserem Reducer um diese ergänzen. Für dieses kleine Beispiel sollte eine action jedoch erstmal ausreichen.

 

Den Redux Store erstellen

Nun müssen wir nur noch den sogenannten Store erstellen. Dafür bekommen wir von redux bereits eine Funktion createStore(), die einen Reducer oder Kombinierte Reducer als Parameter bekommt zur Verfügung gestellt.

Die neue app.js sieht wie folgt aus:

Redux selbst empfiehlt den Store über eine spezielle React Redux-Komponente namens „Provider“ einzubinden. Diese Komponente stellt den Store magisch für alle Containerkomponenten in der Anwendung zur Verfügung, ohne ihn explizit zu übergeben.

 

Zu guter letzt passen wir noch unsere Komponenten an

Achtung: hier ein gist, welcher noch nicht fertig ist!

Um alles ein wenig geordneter zu gestalten, werde ich die Komponenten im späteren Verlauf in eigene Dateien auslagern.

Während sich MediaFile und AvailableMedias erstmal nicht geändert haben, ist unsere MediaLibrary Komponente nun auch nur noch eine sogenannte „presentational component“.

State und Funktionen sind ja nun im Reducer.

Was hier noch fehlt.

Nun müssen wir ein paar Containerkomponenten generieren, um sie mit dem Redux-Store zu verbinden. Das kann man in eine extra Datei auslagern, oder auch direkt an die Komponenten anhängen. Das variiert meistens nach Komplexität.

In unserem Beispiel verzichte ich auf extra Dateien für den Container und verbinde die Komponenten direkt mit dem Redux Store.

Die fertigen Komponenten sehen dann so aus:

Kurz erklärt: Wenn wir uns die MediaFile Komponente nun anschauen, haben wir einmal über mapProps und mapDispatch, jeweils das Property „media“ und auch den Callback „selectMedia“ aus unserem store verbunden!

Wir müssen nun keinen Umweg mehr über andere Komponenten gehen, um den state zu lesen oder auch zu updaten.

Damit ist unsere Media Library nun fertig und implementiert React Redux.

 

Zusammenfassung

Redux ist eine tolle Implementation um den state der App zentral und überschaubar zu halten. Es hilft dabei den Code zu strukturieren und vor allem Komponenten wiederzuverwenden!

Ich bedanke mich, dass Du das Tutorial gelesen hast und ich hoffe ich konnte Dir damit Redux ein bisschen näher bringen!

Sollte dir das Tutorial gefallen haben, kannst Du mich unterstützen in dem Du es ganz einfach mit Leuten teilst, denen es eventuell auch weiterhelfen könnte.

Natürlich ist konstruktive Kritik, sowie auch Feedback gewünscht. Lass dafür einfach ein Kommentar da!

Der komplette Code steht auch auf github zur Verfügung, falls Du dir das nochmal im ganzen anschauen möchtest.

PS: Ich konnte mich einfach nicht entscheiden ob ich state & action nun Großschreibe oder nicht. Habe mich da es englische Wörter sind für klein entschieden! 😀

Kommentar verfassen