Как да използвам Flux за управление на състоянието в ReactJS - обяснено с пример

Ако сте започнали да работите по ReactJS наскоро, може би се чудите как да управлявате състоянието в React, така че приложението ви да може да се мащабира.

За да разрешат този проблем с държавното управление, много компании и хора са разработили различни решения. Facebook, който разработи ReactJS, излезе с решение, наречено Flux .

Може да сте чували за Redux, ако сте работили по технология отпред като AngularJS или EmberJS . ReactJS също има библиотека за внедряване на Redux.

Но преди да научите Redux, бих ви посъветвал да преминете през Flux и да го разберете. След това опитайте Redux. Казвам това, защото Redux е по-усъвършенствана версия на Flux. Ако концепциите на Flux са ясни, тогава можете да научите редукс и да го интегрирате във вашето приложение.

Какво е поток?

Flux използва еднопосочен модел на потока на данни, за да реши сложността на управлението на състоянието. Не забравяйте, че това не е рамка - по-скоро е по-скоро модел, който има за цел да реши проблема с държавното управление.

Чудите ли се какво не е наред със съществуващата MVC рамка? Представете си, че приложението на вашия клиент се увеличава. Имате взаимодействие между много модели и изгледи. Как би изглеждало?

Връзката между компонентите се усложнява. Мащабирането на приложението става трудно. Facebook се сблъска със същия проблем. За да разрешат този проблем, те създадоха еднопосочен поток от данни .

Както можете да видите от изображението по-горе, има много компоненти, използвани във Flux. Нека да разгледаме всички компоненти един по един.

Изглед: този компонент изобразява потребителския интерфейс. Винаги, когато възникне някакво взаимодействие с потребителя (като събитие), то изстрелва действието. Също така, когато Магазинът информира изгледа, че е настъпила някаква промяна, той се изобразява отново. Например, ако потребител щракне върху бутона Добавяне .

Действие: това обработва всички събития. Тези събития се предават от компонента view. Този слой обикновено се използва за извършване на API повиквания. След като действието е направено, то се изпраща с помощта на диспечера. Действието може да бъде нещо като добавяне на публикация, изтриване на публикация или друго взаимодействие с потребителя.

Общата структура на полезния товар за изпращане на събитие е както следва:

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

Ключът actionType е задължителен и се използва от диспечера за предаване на актуализации на свързаното хранилище. Също така е известна практиката да се използват константи, за да се задържи името на стойността за ключ actionType, така че да не се появяват грешки. Данните съдържат информацията за събитието, която искаме да изпратим от Action to Store. Името на този ключ може да бъде всяко.

Диспечер: това е централният регистър на концентратора и сингълтона. Той изпраща полезния товар от Action to Store. Също така гарантира, че няма каскадни ефекти, когато дадено действие се изпраща до магазина. Той гарантира, че няма да се случи друго действие, преди слоят данни да завърши операциите по обработка и съхранение.

Помислете, че този компонент има контролер на трафика в системата. Това е централизиран списък на обратно извикване. Той извиква обратно извикване и излъчва полезния товар, който е получил от действието.

Поради този компонент потокът от данни е предсказуем. Всяко действие актуализира конкретното хранилище с обратно извикване, което е регистрирано в диспечера.

Store: това съдържа състоянието на приложението и е слой данни от този модел. Не го разглеждайте като модел от MVC. Приложението може да има един или много магазини за приложения. Магазините се актуализират, защото имат обратно обаждане, регистрирано с помощта на диспечера.

Излъчвателят на събития на възел се използва за актуализиране на магазина и излъчване на актуализацията за преглед. Изгледът никога не актуализира директно състоянието на приложението. Актуализира се поради промените в магазина.

Това е само част от Flux, която може да актуализира данните. Интерфейсите, внедрени в магазина, са както следва:

  1. В EventEmitter се удължава за да информира, че хранилище за данни е актуализирана.
  2. Добавят се слушатели като addChangeListener и removeChangeListener .
  3. emitChange се използва за излъчване на промяната.

Помислете за горната диаграма с повече магазини и изгледи. И все пак моделът и потокът от данни ще бъдат едни и същи. Това е така, защото това е еднопосочен и предсказуем поток от данни, за разлика от MVC или двупосочно свързване. Това подобрява последователността на данните и е по- лесно да се намери грешката .

Е, Flux носи следните ключови предимства на таблицата с помощта на еднопосочен поток от данни:

  1. Кодът става съвсем ясен и лесен за разбиране.
  2. Лесно тестируем с помощта на Unit Test.
  3. Могат да се създават скалируеми приложения.
  4. Предсказуем поток от данни.
Забележка: Единственият недостатък на Flux е, че има някакъв шаблон, който трябва да напишем. Освен шаблона, има малко код, който трябва да напишем при добавяне на компоненти към съществуващото приложение.

Шаблон за кандидатстване

За да научим как да внедряваме flux в ReactJS, ще изградим страница за публикации. Тук ще покажем всички публикации. Шаблонът на приложението е наличен при този ангажимент. Ще използваме това като шаблон за интегриране на Flux върху него.

За да клонирате кода от този коммит, използвайте следната команда:

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

Ще ни е необходим модул за реакция-рутер-dom и 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.