Cómo usar Flux para administrar el estado en ReactJS: explicado con un ejemplo

Si ha comenzado a trabajar en ReactJS recientemente, es posible que se pregunte cómo administrar el estado en React para que su aplicación pueda escalar.

Para resolver este problema de gestión estatal, muchas empresas y personas han desarrollado varias soluciones. Facebook, que desarrolló ReactJS, ideó una solución llamada Flux .

Es posible que haya oído hablar de Redux si ha trabajado en tecnología de interfaz de usuario como AngularJS o EmberJS . ReactJS también tiene una biblioteca para implementar Redux.

Pero antes de aprender Redux, le aconsejaría que pasara por Flux y lo entendiera. Después de eso, dale una oportunidad a Redux. Digo esto porque Redux es una versión más avanzada de Flux. Si los conceptos de Flux son claros, entonces puede aprender redux e integrarlo en su aplicación.

¿Qué es el flujo?

Flux utiliza un patrón de flujo de datos unidireccional para resolver la complejidad de la gestión del estado. Recuerde que no es un marco, sino más bien un patrón que tiene como objetivo resolver el problema de la gestión estatal.

¿Se pregunta qué está mal con el marco MVC existente? Imagine que la aplicación de su cliente se amplía. Tiene interacción entre muchos modelos y vistas. ¿Cómo se vería?

La relación entre componentes se complica. Se vuelve difícil escalar la aplicación. Facebook enfrentó el mismo problema. Para resolver este problema, diseñaron un flujo de datos unidireccional .

Como puede ver en la imagen de arriba, hay muchos componentes utilizados en Flux. Repasemos todos los componentes uno por uno.

Ver: este componente representa la interfaz de usuario. Cada vez que se produce una interacción del usuario (como un evento), se activa la acción. Además, cuando la Tienda informa a la Vista que se ha producido algún cambio, se vuelve a renderizar. Por ejemplo, si un usuario hace clic en el botón Agregar .

Acción: maneja todos los eventos. Estos eventos los pasa el componente de vista. Esta capa se usa generalmente para realizar llamadas a la API. Una vez que se realiza la acción, se envía mediante el Dispatcher. La acción puede ser algo como agregar una publicación, eliminar una publicación o cualquier otra interacción del usuario.

La estructura común de la carga útil para enviar un evento es la siguiente:

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

La clave actionType es obligatoria y el despachador la utiliza para pasar actualizaciones a la tienda relacionada. También es una práctica conocida utilizar constantes para mantener el nombre del valor de la tecla actionType para que no se produzcan errores tipográficos. Los datos contienen la información del evento que queremos enviar de Action a Store. El nombre de esta clave puede ser cualquier cosa.

Dispatcher: este es el registro central y singleton. Envía la carga útil de Actions a Store. También se asegura de que no haya efectos en cascada cuando se envía una acción a la tienda. Garantiza que no ocurra ninguna otra acción antes de que la capa de datos haya completado las operaciones de procesamiento y almacenamiento.

Considere que este componente tiene un controlador de tráfico en el sistema. Es una lista centralizada de devoluciones de llamada. Invoca la devolución de llamada y difunde la carga útil que recibió de la acción.

Debido a este componente, el flujo de datos es predecible. Cada acción actualiza la tienda específica con la devolución de llamada que está registrada con el despachador.

Tienda: contiene el estado de la aplicación y es una capa de datos de este patrón. No lo considere como un modelo de MVC. Una aplicación puede tener una o varias tiendas de aplicaciones. Las tiendas se actualizan porque tienen una devolución de llamada que se registra mediante el despachador.

El emisor de eventos del nodo se usa para actualizar la tienda y transmitir la actualización para ver. La vista nunca actualiza directamente el estado de la aplicación. Se actualiza debido a los cambios en la tienda.

Esta es solo una parte de Flux que puede actualizar los datos. Las interfaces implementadas en la tienda son las siguientes:

  1. El EventEmitter se amplía para informar a la vista que los datos de la tienda se han actualizado.
  2. Se agregan oyentes como addChangeListener y removeChangeListener .
  3. emitChange se utiliza para emitir el cambio.

Considere el diagrama anterior con más tiendas y vistas. Aún así, el patrón y el flujo de datos serán los mismos. Esto se debe a que se trata de una dirección única y un flujo de datos predecible, a diferencia de MVC o enlace bidireccional. Esto mejora la coherencia de los datos y es más fácil encontrar el error .

Bueno, Flux trae los siguientes beneficios clave a la mesa con la ayuda del flujo de datos unidireccional:

  1. El código se vuelve bastante claro y fácil de entender.
  2. Fácilmente comprobable mediante Unit Test.
  3. Se pueden crear aplicaciones escalables.
  4. Flujo de datos predecible.
Nota: El único inconveniente del Flux es que hay un texto estándar que necesitamos escribir. Además del texto estándar, hay poco código que necesitamos escribir al agregar componentes a la aplicación existente.

Plantilla de aplicación

Para aprender cómo implementar flux en ReactJS, crearemos una página de Publicaciones. Aquí mostraremos todas las publicaciones. La plantilla de la aplicación está disponible en este compromiso. Usaremos esto como plantilla para integrar Flux encima.

Para clonar el código de esta confirmación, use el siguiente comando:

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

Necesitaremos un módulo react-router-dom y bootstrap . Para instalar estos paquetes, use el siguiente comando:

npm install [email protected] [email protected] 

Una vez hecho esto, verá la siguiente aplicación:

Para comprender Flux en detalle, solo implementaremos la página de publicaciones GET . Una vez hecho esto, se dará cuenta de que el proceso es el mismo para PUBLICAR , EDITAR y ELIMINAR .

Aquí verá la siguiente estructura de directorio:

+-- 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
Nota: Hemos agregado aquí un db.json  archivo. Este es un archivo de datos ficticios. Como no queremos crear API y, en cambio, centrarnos en Flux, recuperaremos los datos de este archivo.

El componente base de nuestra aplicación es index.js. Aquí hemos renderizado el App.jsinterior del index.htmldirectorio público bajo usando los métodos render y getElementById . Se App.jsutiliza para configurar las rutas.

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.

Con esto, vaya a la página del blog y verá que nuestra aplicación de blog está funcionando. La única diferencia es que ahora en lugar de leer los datos en el método useEffect los estamos leyendo en acciones, almacenándolos en la tienda y enviándolos a los componentes que lo requieran.

Cuando utilice la API real, encontrará que la aplicación carga los datos de la API una vez y los almacena en la tienda. Cuando volvamos a visitar la misma página, observará que no es necesario volver a llamar a la API. Puede monitorearlo en la pestaña de origen en la consola de desarrollo de Chrome.

¡¡Y terminamos !! Espero que este tutorial haya aclarado la idea de Flux y pueda utilizarlo en sus proyectos.

No dudes en conectarte conmigo en Twitter y Github.