Τρόπος χρήσης της ροής για τη διαχείριση της κατάστασης στο ReactJS - Εξηγείται με ένα παράδειγμα

Εάν έχετε αρχίσει να εργάζεστε στο ReactJS πρόσφατα, τότε ίσως αναρωτιέστε πώς να διαχειριστείτε την κατάσταση στο React, έτσι ώστε η εφαρμογή σας να μπορεί να κλιμακωθεί.

Για την επίλυση αυτού του ζητήματος διαχείρισης κράτους, πολλές εταιρείες και άνθρωποι έχουν αναπτύξει διάφορες λύσεις. Το Facebook, που ανέπτυξε το ReactJS, βρήκε μια λύση που ονομάζεται Flux .

Μπορεί να έχετε ακούσει για το Redux εάν έχετε εργαστεί σε τεχνολογία front end όπως το AngularJS ή το EmberJS . Το ReactJS διαθέτει επίσης βιβλιοθήκη για την εφαρμογή του Redux.

Αλλά πριν μάθετε το Redux θα σας συμβούλευα να περάσετε από το Flux και να το καταλάβετε. Μετά από αυτό δοκιμάστε τη Redux. Το λέω αυτό επειδή το Redux είναι μια πιο προηγμένη έκδοση του Flux. Εάν οι έννοιες του Flux είναι σαφείς τότε μπορείτε να μάθετε το redux και να το ενσωματώσετε στην εφαρμογή σας.

Τι είναι η ροή;

Το Flux χρησιμοποιεί ένα μονοκατευθυντικό μοτίβο ροής δεδομένων για την επίλυση της πολυπλοκότητας διαχείρισης κατάστασης. Θυμηθείτε ότι δεν είναι ένα πλαίσιο - μάλλον είναι ένα μοτίβο που στοχεύει στην επίλυση του ζητήματος της διαχείρισης του κράτους.

Αναρωτιέστε τι συμβαίνει με το υπάρχον πλαίσιο MVC; Φανταστείτε την εφαρμογή του πελάτη σας να αυξάνεται. Έχετε αλληλεπίδραση μεταξύ πολλών μοντέλων και προβολών. Πώς θα φαινόταν;

Η σχέση μεταξύ των συστατικών γίνεται περίπλοκη. Γίνεται δύσκολο να κλιμακωθεί η εφαρμογή. Το Facebook αντιμετώπισε το ίδιο πρόβλημα. Για να επιλύσουν αυτό το ζήτημα, αρχειοθέτησαν μια μονή κατευθυντική ροή δεδομένων .

Όπως μπορείτε να δείτε από την παραπάνω εικόνα, υπάρχουν πολλά συστατικά που χρησιμοποιούνται στο Flux. Ας δούμε όλα τα συστατικά ένα προς ένα.

Προβολή: αυτό το στοιχείο αποδίδει τη διεπαφή χρήστη. Κάθε φορά που συμβαίνει οποιαδήποτε αλληλεπίδραση χρήστη σε αυτό (όπως ένα συμβάν) τότε ενεργοποιείται η ενέργεια. Επίσης, όταν το Κατάστημα ενημερώνει την Προβολή ότι έχει προκύψει κάποια αλλαγή, εμφανίζεται ξανά. Για παράδειγμα, εάν ένας χρήστης κάνει κλικ στο κουμπί Προσθήκη .

Δράση: χειρίζεται όλες τις εκδηλώσεις. Αυτά τα συμβάντα περνούν από το στοιχείο προβολής. Αυτό το επίπεδο χρησιμοποιείται γενικά για την πραγματοποίηση κλήσεων API. Μόλις ολοκληρωθεί η ενέργεια, αποστέλλεται χρησιμοποιώντας το Dispatcher. Η ενέργεια μπορεί να είναι όπως να προσθέσετε μια ανάρτηση, να διαγράψετε μια ανάρτηση ή οποιαδήποτε άλλη αλληλεπίδραση χρήστη.

Η κοινή δομή του ωφέλιμου φορτίου για την αποστολή ενός συμβάντος έχει ως εξής:

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

Το κλειδί actionType είναι υποχρεωτικό και χρησιμοποιείται από τον διεκπεραιωτή για τη μετάδοση ενημερώσεων στο σχετικό κατάστημα. Είναι επίσης γνωστή πρακτική να χρησιμοποιείτε σταθερές για να κρατάτε το όνομα τιμής για το κλειδί actionType, ώστε να μην εμφανίζονται τυπογραφικά λάθη. Τα δεδομένα διατηρούν τις πληροφορίες συμβάντος που θέλουμε να στείλουμε από το Action στο Store. Το όνομα για αυτό το κλειδί μπορεί να είναι οτιδήποτε.

Dispatcher: αυτό είναι το κεντρικό μητρώο και το μοναδικό μητρώο. Αποστέλλει το ωφέλιμο φορτίο από το Actions στο Store. Επίσης, βεβαιωθείτε ότι δεν υπάρχουν επεισόδια εφέ όταν αποστέλλεται μια ενέργεια στο κατάστημα. Διασφαλίζει ότι καμία άλλη ενέργεια δεν θα συμβεί προτού ολοκληρωθεί η επεξεργασία και αποθήκευση των επιπέδων δεδομένων.

Λάβετε υπόψη ότι αυτό το στοιχείο διαθέτει ελεγκτή κίνησης στο σύστημα. Είναι μια κεντρική λίστα επιστροφών. Επικαλείται την επιστροφή κλήσης και μεταδίδει το ωφέλιμο φορτίο που έλαβε από την ενέργεια.

Λόγω αυτού του στοιχείου, η ροή δεδομένων είναι προβλέψιμη. Κάθε ενέργεια ενημερώνει το συγκεκριμένο κατάστημα με την επιστροφή κλήσης που είναι εγγεγραμμένη στον αποστολέα.

Store: διατηρεί την κατάσταση της εφαρμογής και είναι ένα επίπεδο δεδομένων αυτού του μοτίβου. Μην το θεωρείτε ως μοντέλο της MVC. Μια εφαρμογή μπορεί να έχει ένα ή πολλά καταστήματα εφαρμογών. Τα καταστήματα ενημερώνονται επειδή έχουν μια επιστροφή κλήσης που είναι καταχωρημένη μέσω του διεκπεραιωτή.

Ο εκδότης συμβάντος του κόμβου χρησιμοποιείται για την ενημέρωση του καταστήματος και τη μετάδοση της ενημέρωσης για προβολή. Η προβολή δεν ενημερώνει ποτέ άμεσα την κατάσταση της εφαρμογής. Ενημερώνεται λόγω των αλλαγών στο κατάστημα.

Αυτό είναι μόνο μέρος του Flux που μπορεί να ενημερώσει τα δεδομένα. Οι διεπαφές που εφαρμόζονται στο κατάστημα είναι οι εξής:

  1. Το EventEmitter επεκτείνεται για να ενημερώσει την άποψη ότι τα δεδομένα του χώρου αποθήκευσης έχουν ενημερωθεί.
  2. Προστίθενται ακροατές όπως addChangeListener και removeChangeListener .
  3. Το emitChange χρησιμοποιείται για την εκπομπή της αλλαγής.

Εξετάστε το παραπάνω διάγραμμα με περισσότερα καταστήματα και προβολές. Ωστόσο, το μοτίβο και η ροή δεδομένων θα είναι τα ίδια. Αυτό συμβαίνει επειδή πρόκειται για μονή κατεύθυνση και προβλέψιμη ροή δεδομένων, σε αντίθεση με MVC ή αμφίδρομη σύνδεση. Αυτό βελτιώνει τη συνοχή των δεδομένων και είναι πιο εύκολο να εντοπίσετε το σφάλμα .

Λοιπόν, το Flux φέρνει τα ακόλουθα βασικά οφέλη στον πίνακα με τη βοήθεια της μονής κατεύθυνσης ροής δεδομένων:

  1. Ο κωδικός γίνεται αρκετά σαφής και κατανοητός.
  2. Εύκολη δοκιμή χρησιμοποιώντας το Test Unit.
  3. Μπορούν να δημιουργηθούν επεκτάσιμες εφαρμογές.
  4. Προβλέψιμη ροή δεδομένων.
Σημείωση: Το μόνο μειονέκτημα με το Flux είναι ότι υπάρχει κάποιο boilerplate που πρέπει να γράψουμε. Εκτός από το boilerplate, υπάρχει λίγος κώδικας που πρέπει να γράψουμε κατά την προσθήκη στοιχείων στην υπάρχουσα εφαρμογή.

Πρότυπο εφαρμογής

Για να μάθουμε πώς να εφαρμόζουμε τη ροή στο ReactJS, θα δημιουργήσουμε μια σελίδα δημοσιεύσεων. Εδώ θα εμφανίσουμε όλες τις δημοσιεύσεις. Το πρότυπο εφαρμογής είναι διαθέσιμο σε αυτήν τη δέσμευση. Θα το χρησιμοποιήσουμε ως πρότυπο για την ενσωμάτωση του Flux.

Για να κλωνοποιήσετε τον κώδικα από αυτήν τη δέσμευση, χρησιμοποιήστε την ακόλουθη εντολή:

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

Θα χρειαζόμαστε μια μονάδα αντιδραστήρα-αντιδραστήρα και bootstrap . Για να εγκαταστήσετε αυτά τα πακέτα, χρησιμοποιήστε την ακόλουθη εντολή:

npm install [email protected] [email protected] 

Μόλις τελειώσετε, θα δείτε την ακόλουθη εφαρμογή:

Για να κατανοήσουμε λεπτομερώς το Flux θα εφαρμόσουμε μόνο τη σελίδα δημοσιεύσεων GET . Μόλις γίνει αυτό, θα συνειδητοποιήσετε ότι η διαδικασία είναι η ίδια για POST , EDIT και DELETE .

Εδώ θα δείτε την ακόλουθη δομή καταλόγου:

+-- 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
Σημείωση: Έχουμε προσθέσει εδώ ένα db.json  αρχείο. Αυτό είναι ένα εικονικό αρχείο δεδομένων. Δεδομένου ότι δεν θέλουμε να δημιουργήσουμε API και αντί να επικεντρωθούμε στο Flux, θα ανακτήσουμε τα δεδομένα από αυτό το αρχείο.

Το βασικό συστατικό της εφαρμογής μας είναι index.js. Εδώ έχουμε αποδώσει τον App.jsεσωτερικό index.htmlδημόσιο κατάλογο χρησιμοποιώντας τις μεθόδους render και getElementById . Ο App.jsχρησιμοποιείται για τη διαμόρφωση των διαδρομών.

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.

Με αυτό μεταβείτε στη σελίδα ιστολογίου και θα διαπιστώσετε ότι η εφαρμογή ιστολογίου μας λειτουργεί. Η μόνη διαφορά είναι ότι αντί να διαβάζουμε τα δεδομένα στη μέθοδο useEffect τα διαβάζουμε σε ενέργειες, τα αποθηκεύουμε στο κατάστημα και τα στέλνουμε στα στοιχεία που το απαιτούν.

Όταν χρησιμοποιείτε το πραγματικό API θα διαπιστώσετε ότι η εφαρμογή φορτώνει τα δεδομένα από το API μία φορά και τα αποθηκεύει στο κατάστημα. Όταν επανεξετάσουμε την ίδια σελίδα θα παρατηρήσετε ότι δεν απαιτείται ξανά κλήση API. Μπορείτε να το παρακολουθήσετε στην καρτέλα προέλευσης στην Κονσόλα προγραμματιστή Chrome.

Και τελειώσαμε !! Ελπίζω ότι αυτό το σεμινάριο έχει καταστήσει την ιδέα του Flux πιο ξεκάθαρη και θα μπορείτε να τη χρησιμοποιήσετε στα έργα σας.

Μη διστάσετε να συνδεθείτε μαζί μου στο Twitter και το Github.