Verwendung von Flux zum Verwalten des Status in ReactJS - Erklärt anhand eines Beispiels

Wenn Sie kürzlich mit der Arbeit an ReactJS begonnen haben, fragen Sie sich möglicherweise, wie Sie den Status in React verwalten können, damit Ihre Anwendung skaliert werden kann.

Um dieses Problem der staatlichen Verwaltung zu lösen, haben viele Unternehmen und Personen verschiedene Lösungen entwickelt. Facebook, das ReactJS entwickelt hat, hat eine Lösung namens Flux entwickelt .

Sie haben vielleicht schon von Redux gehört, wenn Sie an Front-End-Technologien wie AngularJS oder EmberJS gearbeitet haben . ReactJS verfügt auch über eine Bibliothek zur Implementierung von Redux.

Aber bevor Sie Redux lernen, würde ich Ihnen raten, durch Flux zu gehen und es zu verstehen. Probieren Sie danach Redux aus. Ich sage das, weil Redux eine fortgeschrittenere Version von Flux ist. Wenn die Konzepte von Flux klar sind, können Sie Redux lernen und in Ihre Anwendung integrieren.

Was ist Fluss?

Flux verwendet ein unidirektionales Datenflussmuster , um die Komplexität der Zustandsverwaltung zu lösen. Denken Sie daran, es ist kein Rahmen, sondern eher ein Muster, das darauf abzielt, das Problem der staatlichen Verwaltung zu lösen.

Fragen Sie sich, was mit dem vorhandenen MVC-Framework nicht stimmt? Stellen Sie sich vor, die Anwendung Ihres Kunden wird vergrößert. Sie haben Interaktion zwischen vielen Modellen und Ansichten. Wie würde es aussehen?

Die Beziehung zwischen Komponenten wird kompliziert. Es wird schwierig, die Anwendung zu skalieren. Facebook stand vor dem gleichen Problem. Um dieses Problem zu lösen, wurde ein Datenfluss mit einer Richtung erstellt .

Wie Sie aus dem obigen Bild sehen können, werden in Flux viele Komponenten verwendet. Lassen Sie uns alle Komponenten einzeln durchgehen.

Ansicht: Diese Komponente rendert die Benutzeroberfläche. Immer wenn eine Benutzerinteraktion darauf stattfindet (wie bei einem Ereignis), wird die Aktion ausgelöst. Auch wenn der Store die Ansicht darüber informiert, dass Änderungen vorgenommen wurden, wird er selbst neu gerendert. Zum Beispiel, wenn ein Benutzer auf die Schaltfläche Hinzufügen klickt .

Aktion: Hiermit werden alle Ereignisse behandelt. Diese Ereignisse werden von der Ansichtskomponente übergeben. Diese Schicht wird im Allgemeinen zum Ausführen von API-Aufrufen verwendet. Sobald die Aktion abgeschlossen ist, wird sie mit dem Dispatcher versendet. Die Aktion kann so etwas wie das Hinzufügen eines Beitrags, das Löschen eines Beitrags oder eine andere Benutzerinteraktion sein.

Die übliche Struktur der Nutzdaten zum Versenden eines Ereignisses lautet wie folgt:

{ actionType: "", data: { title: "Understanding Flux step by step", author: "Sharvin" } }

Der actionType-Schlüssel ist obligatorisch und wird vom Dispatcher verwendet, um Aktualisierungen an den zugehörigen Speicher zu übergeben. Es ist auch bekannt, Konstanten zu verwenden, um den Wertnamen für den actionType-Schlüssel zu speichern, damit keine Tippfehler auftreten. Daten enthalten die Ereignisinformationen, die wir von Aktion an Speicher senden möchten. Der Name für diesen Schlüssel kann beliebig sein.

Dispatcher: Dies ist die zentrale Hub- und Singleton-Registrierung. Es sendet die Nutzdaten von Actions an Store. Stellen Sie außerdem sicher, dass beim Versenden einer Aktion an das Geschäft keine Kaskadeneffekte auftreten. Es stellt sicher, dass keine andere Aktion ausgeführt wird, bevor die Datenschicht die Verarbeitungs- und Speichervorgänge abgeschlossen hat.

Angenommen, diese Komponente verfügt über einen Verkehrscontroller im System. Es ist eine zentralisierte Liste von Rückrufen. Es ruft den Rückruf auf und sendet die von der Aktion empfangenen Nutzdaten.

Aufgrund dieser Komponente ist der Datenfluss vorhersehbar. Jede Aktion aktualisiert den spezifischen Speicher mit dem Rückruf, der beim Dispatcher registriert ist.

Speichern: Dies enthält den App-Status und ist eine Datenschicht dieses Musters. Betrachten Sie es nicht als Modell von MVC. Eine Anwendung kann einen oder mehrere App Stores haben. Geschäfte werden aktualisiert, weil sie einen Rückruf haben, der über den Dispatcher registriert wird.

Der Ereignisemitter des Knotens wird verwendet, um den Speicher zu aktualisieren und das Update zur Anzeige zu senden. Die Ansicht aktualisiert den Anwendungsstatus niemals direkt. Es wird aufgrund der Änderungen am Geschäft aktualisiert.

Dies ist nur ein Teil von Flux, der die Daten aktualisieren kann. Im Store implementierte Schnittstellen sind wie folgt:

  1. Der EventEmitter wird erweitert, um die Ansicht darüber zu informieren, dass die Speicherdaten aktualisiert wurden.
  2. Listener wie addChangeListener und removeChangeListener werden hinzugefügt.
  3. emitChange wird verwendet, um die Änderung auszugeben .

Betrachten Sie das obige Diagramm mit mehr Speichern und Ansichten. Das Muster und der Datenfluss sind jedoch gleich. Dies liegt daran, dass dies im Gegensatz zu MVC oder Zwei-Wege-Bindung eine einzige Richtung und ein vorhersagbarer Datenfluss ist. Dies verbessert die Datenkonsistenz und es ist einfacher, den Fehler zu finden .

Nun, Flux bringt mithilfe des unidirektionalen Datenflusses die folgenden Hauptvorteile auf den Tisch :

  1. Der Code wird ziemlich klar und leicht zu verstehen.
  2. Mit Unit Test leicht zu testen.
  3. Skalierbare Apps können erstellt werden.
  4. Vorhersehbarer Datenfluss.
Hinweis: Der einzige Nachteil des Flux ist, dass es eine Boilerplate gibt, die wir schreiben müssen. Neben dem Boilerplate gibt es wenig Code, den wir schreiben müssen, wenn wir Komponenten zur vorhandenen Anwendung hinzufügen.

Anwendungsvorlage

Um zu lernen, wie Flux in ReactJS implementiert wird, erstellen wir eine Posts-Seite. Hier werden alle Beiträge angezeigt. Die Anwendungsvorlage ist bei diesem Commit verfügbar. Wir werden dies als Vorlage für die Integration von Flux verwenden.

Verwenden Sie den folgenden Befehl, um den Code aus diesem Commit zu klonen:

git clone //github.com/Sharvin26/DummyBlog.git
git checkout 0d56987b2d461b794e7841302c9337eda1ad0725

Wir benötigen ein React-Router-Dom und ein Bootstrap- Modul. Verwenden Sie den folgenden Befehl, um diese Pakete zu installieren:

npm install [email protected] [email protected] 

Sobald Sie fertig sind, sehen Sie die folgende Anwendung:

Um Flux im Detail zu verstehen, implementieren wir nur die Seite GET- Beiträge. Sobald dies erledigt ist, werden Sie feststellen, dass der Prozess für POST , EDIT und DELETE der gleiche ist .

Hier sehen Sie folgende Verzeichnisstruktur:

+-- README.md +-- package-lock.json +-- package.json +-- node_modules +-- .gitignore +-- public | +-- index.html +-- src | +-- +-- components | +-- +-- +-- common | +-- +-- +-- +-- NavBar.js | +-- +-- +-- PostLists.js | +-- +-- pages | +-- +-- +-- Home.js | +-- +-- +-- NotFound.js | +-- +-- +-- Posts.js | +-- index.js | +-- App.js | +-- db.json
Hinweis: Wir haben hier eine db.json  Datei hinzugefügt . Dies ist eine Dummy-Datendatei. Da wir keine APIs erstellen und uns stattdessen auf Flux konzentrieren möchten, werden wir die Daten aus dieser Datei abrufen.

Die Basiskomponente unserer Anwendung ist index.js. Hier haben wir das App.jsinnere index.htmlVerzeichnis unter public mit den Methoden render und getElementById gerendert . Das App.jswird zum Konfigurieren der Routen verwendet.

We are also adding NavBar component at the top of the other so it will be available for all the components.

Inside the pages directory we have 3 files =>Home.js, Posts.js, and NotFound.js. Home.js  is simply used to display the Home component. When a user routes to a URL which doesn't exist, then NotFound.js renders.

The Posts.js is the parent component and it is used to get the data from the db.json file. It passes this data to the PostLists.js under the components directory. This component is a dumb component and it only handles the UI. It gets the data as props from its parent component (Posts.js) and displays it in the form of cards.

Now that we are clear about how our blog app is working we will start with integrating Flux on top of it.

Integrating Flux

Install Flux using the following command:

npm install [email protected]

To integrate Flux in our application we will divide this section into 4 subsections:

  1. Dispatcher
  2. Actions
  3. Stores
  4. View

Note: The complete code is available at this repository.

Dispatcher

First, create two new folders named actions and stores under the src directory. After that create a file named appDispatcher.js  under the same src directory.

Note: From now all the files which are related to Flux will have Camel casing as they are not ReactJS components.

Go to the appDispatcher.js and copy-paste the following code:

import { Dispatcher } from "flux"; const dispatcher = new Dispatcher(); export default dispatcher; 

Here we are importing the Dispatcher from the flux library that we installed, creating a new object and exporting it so that our actions module can use it.

Actions

Now go to the actions directory and create two files named actionTypes.js and postActions.js.  In the actionTypes.js we will define the constants that we require in postActions.js and store module.

The reason behind defining constants is that we don't want to make typos. You don't have to define constants but it is generally considered a good practice.

// actionTypes.js export default { GET_POSTS: "GET_POSTS", }; 

Now inside the postActions.js, we will retrieve the data from db.json and use the dispatcher object to dispatch it.

//postActions.js import dispatcher from "../appDispatcher"; import actionTypes from "./actionTypes"; import data from "../db.json"; export function getPosts() { dispatcher.dispatch({ actionTypes: actionTypes.GET_POSTS, posts: data["posts"], }); } 

Here in the above code, we have imported the dispatcher object, actionTypes constant, and data. We are using a dispatcher object's dispatch method to send the data to the store. The data in our case will be sent in the following format:

{ actionTypes: "GET_POSTS", posts: [ { "id": 1, "title": "Hello World", "author": "Sharvin Shah", "body": "Example of blog application" }, { "id": 2, "title": "Hello Again", "author": "John Doe", "body": "Testing another component" } ] }

Stores

Now we need to build the store which will act as a data layer for storing the posts. It will have an event listener to inform the view that something has changed, and will register using dispatcher with the actions to get the data.

Go to the store directory and create a new file called postStore.js.  Now first, we will import EventEmitter from the Events package. It is available in the NodeJS by default. We will also import the dispatcher object and actionTypes constant file here.

import { EventEmitter } from "events"; import dispatcher from "../appDispatcher"; import actionTypes from "../actions/actionTypes"; 

We will declare the constant of the change event and a variable to hold the posts whenever the dispatcher passes it.

const CHANGE_EVENT = "change"; let _posts = [];

Now we will write a class that extends the EventEmitter as its base class. We will declare the following methods in this class:

addChangeListener: It uses the NodeJS EventEmitter.on. It adds a change listener that accepts the callback function.

removeChangeListener: It uses the NodeJS EventEmitter.removeListener. Whenever we don't want to listen for a specific event we use the following method.

emitChange: It uses the NodeJS EventEmitter.emit. Whenever any change occurs, it emits that change.

This class will also have a method called getPosts which returns the variable _posts that we have declared above the class.

Below the variable declaration add the following code:

class PostStore extends EventEmitter { addChangeListener(callback) { this.on(CHANGE_EVENT, callback); } removeChangeListener(callback) { this.removeListener(CHANGE_EVENT, callback); } emitChange() { this.emit(CHANGE_EVENT); } getPosts() { return _posts; } }

Now create the store object of our PostStore class. We will export this object so that we can use it in the view.

const store = new PostStore();

After that, we will use the dispatcher's register method to receive the payload from our Actions component.

To register for the specific event, we need to use the actionTypes value and determine which action has occurred and process the data accordingly. Add the following code below the object declaration:

dispatcher.register((action) => { switch (action.actionTypes) { case actionTypes.GET_POSTS: _posts = action.posts; store.emitChange(); break; default: } });

We will export the object from this module so others can use it.

export default store;

View

Now we will update our view to send the event to postActions  whenever our Posts page is loaded and receive the payload from the postStore. Go to Posts.js under the pages directory. You'll find the following code inside the useEffect method:

useEffect(() => { setposts(data["posts"]); }, []);

We will change how our useEffect reads and updates the data. First, we will use the addChangeListener method from the postStore class and we will pass an onChange callback to it. We will set the postsstate value to have a return value of the getPosts method from the postStore.js file.

At the start, the store will return an empty array as there is no data available. So we will call a getPostsmethod from the postActions.js. This method will read the data and pass it to the store. Then the store will emit the change and addChangeListener will listen to the change and update the value of the posts  in its onChange callback.

If this seems confusing don't worry – check out the flow chart below which makes it easier to understand.

Remove the old code and update the following code inside Posts.js:

import React, { useState, useEffect } from "react"; import PostLists from "../components/PostLists"; import postStore from "../stores/postStore"; import { getPosts } from "../actions/postActions"; function PostPage() { const [posts, setPosts] = useState(postStore.getPosts()); useEffect(() => { postStore.addChangeListener(onChange); if (postStore.getPosts().length === 0) getPosts(); return () => postStore.removeChangeListener(onChange); }, []); function onChange() { setPosts(postStore.getPosts()); } return ( ); } export default PostPage; 

Here you'll find that we have also removed the import and also we are using setPosts inside our callback instead of useEffect method. The return () => postStore.removeChangeListener(onChange); is used to remove the listener once the user leaves that page.

Wenn Sie dies tun, gehen Sie zur Blog-Seite und Sie werden feststellen, dass unsere Blog-App funktioniert. Der einzige Unterschied besteht darin, dass wir die Daten jetzt nicht mehr in der useEffect- Methode lesen, sondern in Aktionen lesen, im Speicher speichern und an die Komponenten senden, die sie benötigen.

Wenn Sie die eigentliche API verwenden, werden Sie feststellen, dass die Anwendung die Daten einmal aus der API lädt und im Speicher speichert. Wenn wir dieselbe Seite erneut besuchen, werden Sie feststellen, dass kein API-Aufruf erneut erforderlich ist. Sie können es auf der Registerkarte "Quelle" in der Chrome Developer-Konsole überwachen.

Und wir sind fertig !! Ich hoffe, dieses Tutorial hat die Idee von Flux klarer gemacht und Sie können es in Ihren Projekten verwenden.

Fühlen Sie sich frei, mit mir auf Twitter und Github zu verbinden.