Comment utiliser Flux pour gérer l'état dans ReactJS - expliqué avec un exemple

Si vous avez récemment commencé à travailler sur ReactJS, vous vous demandez peut-être comment gérer l'état dans React afin que votre application puisse évoluer.

Pour résoudre ce problème de gestion de l'État, de nombreuses entreprises et personnes ont développé diverses solutions. Facebook, qui a développé ReactJS, a proposé une solution appelée Flux .

Vous avez peut-être entendu parler de Redux si vous avez travaillé sur des technologies frontales telles que AngularJS ou EmberJS . ReactJS dispose également d'une bibliothèque pour implémenter Redux.

Mais avant d'apprendre Redux, je vous conseillerais de passer par Flux et de le comprendre. Après cela, essayez Redux. Je dis cela parce que Redux est une version plus avancée de Flux. Si les concepts de Flux sont clairs, vous pouvez apprendre le redux et l'intégrer dans votre application.

Qu'est-ce que le flux?

Flux utilise un modèle de flux de données unidirectionnel pour résoudre la complexité de la gestion des états. Rappelez-vous qu'il ne s'agit pas d'un cadre, mais plutôt d'un modèle qui vise à résoudre le problème de la gestion de l'état.

Vous vous demandez ce qui ne va pas avec le framework MVC existant? Imaginez que l'application de votre client évolue. Vous avez une interaction entre de nombreux modèles et vues. À quoi cela ressemblerait-il?

La relation entre les composants se complique. Il devient difficile de faire évoluer l'application. Facebook a été confronté au même problème. Pour résoudre ce problème, ils ont conçu un flux de données directionnel unique .

Comme vous pouvez le voir sur l'image ci-dessus, de nombreux composants sont utilisés dans Flux. Passons en revue tous les composants un par un.

Vue: ce composant rend l'interface utilisateur. Chaque fois qu'une interaction de l'utilisateur se produit dessus (comme un événement), il déclenche l'action. De plus, lorsque le magasin informe la vue qu'un changement s'est produit, il se restitue. Par exemple, si un utilisateur clique sur le bouton Ajouter .

Action: cela gère tous les événements. Ces événements sont transmis par le composant de vue. Cette couche est généralement utilisée pour effectuer des appels API. Une fois l'action terminée, elle est distribuée à l'aide du Dispatcher. L'action peut être quelque chose comme ajouter une publication, supprimer une publication ou toute autre interaction de l'utilisateur.

La structure commune de la charge utile pour la distribution d'un événement est la suivante:

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

La clé actionType est obligatoire et elle est utilisée par le répartiteur pour transmettre les mises à jour au magasin associé. Il est également connu d'utiliser des constantes pour contenir le nom de valeur de la clé actionType afin qu'aucune faute de frappe ne se produise. Les données contiennent les informations d'événement que nous souhaitons envoyer d'Action vers Store. Le nom de cette clé peut être n'importe quoi.

Dispatcher: il s'agit du hub central et du registre singleton. Il distribue la charge utile des actions au magasin. S'assure également qu'il n'y a pas d'effets en cascade lorsqu'une action est distribuée au magasin. Il garantit qu'aucune autre action ne se produit avant que la couche de données n'ait terminé les opérations de traitement et de stockage.

Considérez que ce composant a un contrôleur de trafic dans le système. Il s'agit d'une liste centralisée de rappels. Il invoque le rappel et diffuse la charge utile qu'il a reçue de l'action.

En raison de ce composant, le flux de données est prévisible. Chaque action met à jour le magasin spécifique avec le rappel qui est enregistré auprès du répartiteur.

Store: il contient l'état de l'application et constitue une couche de données de ce modèle. Ne le considérez pas comme un modèle de MVC. Une application peut avoir un ou plusieurs magasins d'applications. Les magasins sont mis à jour car ils ont un rappel qui est enregistré à l'aide du répartiteur.

L'émetteur d'événements du nœud est utilisé pour mettre à jour le magasin et diffuser la mise à jour à afficher. La vue ne met jamais directement à jour l'état de l'application. Il est mis à jour en raison des modifications apportées au magasin.

Ce n'est qu'une partie de Flux qui peut mettre à jour les données. Les interfaces implémentées dans le magasin sont les suivantes:

  1. Le EventEmitter est étendu pour informer le point de vue que les données du magasin a été mis à jour.
  2. Des écouteurs comme addChangeListener et removeChangeListener sont ajoutés.
  3. emitChange est utilisé pour émettre le changement.

Considérez le diagramme ci-dessus avec plus de magasins et de vues. Pourtant, le modèle et le flux de données seront les mêmes. En effet, il s'agit d'une direction unique et d'un flux de données prévisible, contrairement à MVC ou à la liaison bidirectionnelle. Cela améliore la cohérence des données et il est plus facile de trouver le bogue .

Eh bien, Flux apporte les avantages clés suivants à la table à l'aide d' un flux de données unidirectionnel:

  1. Le code devient assez clair et facile à comprendre.
  2. Facilement testable à l'aide du test unitaire.
  3. Des applications évolutives peuvent être créées.
  4. Flux de données prévisible.
Remarque: le seul inconvénient du Flux est qu'il y a du passe-partout que nous devons écrire. Outre le passe-partout, il y a peu de code à écrire lors de l'ajout de composants à l'application existante.

Modèle d'application

Pour apprendre à implémenter le flux dans ReactJS, nous allons créer une page Posts. Ici, nous afficherons tous les messages. Le modèle d'application est disponible à ce commit. Nous allons l'utiliser comme modèle pour intégrer Flux par-dessus.

Pour cloner le code de cette validation, utilisez la commande suivante:

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

Nous aurons besoin d'un module react -router-dom et bootstrap . Pour installer ces packages, utilisez la commande suivante:

npm install [email protected] [email protected] 

Une fois terminé, vous verrez l'application suivante:

Pour comprendre Flux en détail, nous n'implémenterons que la page des articles GET . Une fois que cela est fait, vous vous rendrez compte que le processus est le même pour POST , EDIT et DELETE .

Ici, vous verrez la structure de répertoires suivante:

+-- 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
Remarque: nous avons ajouté ici un db.json  fichier. Ceci est un fichier de données factice. Puisque nous ne voulons pas créer d'API et nous concentrer sur Flux, nous allons récupérer les données de ce fichier.

Le composant de base de notre application est index.js. Ici, nous avons rendu l' App.jsintérieur du index.htmlrépertoire public under en utilisant les méthodes render et getElementById . Le App.jsest utilisé pour configurer les routes.

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.

Avec cela, allez sur la page Blog et vous constaterez que notre application de blog fonctionne. La seule différence est qu'au lieu de lire les données dans la méthode useEffect , nous les lisons en actions, les stockons dans le magasin et les envoyons aux composants qui en ont besoin.

Lorsque vous utilisez l'API réelle, vous constaterez que l'application charge les données de l'API une fois et les stocke dans le magasin. Lorsque nous revisiterons la même page, vous remarquerez qu'aucun appel d'API n'est à nouveau requis. Vous pouvez le surveiller sous l'onglet Source de la console Chrome Developer.

Et nous avons terminé !! J'espère que ce tutoriel a clarifié l'idée de Flux et que vous pourrez l'utiliser dans vos projets.

N'hésitez pas à me contacter sur Twitter et Github.