Як використовувати Flux для управління станом у ReactJS - пояснено на прикладі

Якщо ви нещодавно почали працювати над ReactJS, можливо, вам цікаво, як керувати станом у React, щоб програма могла масштабуватися.

Для вирішення цього питання управління державою багато компаній та люди розробили різні рішення. Facebook, який розробив ReactJS, запропонував рішення під назвою Flux .

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

Але перед вивченням Redux я порадив би вам пройти Flux і зрозуміти його. Після цього спробуйте Redux. Я кажу це, тому що Redux - це більш вдосконалена версія Flux. Якщо поняття Flux зрозумілі, тоді ви можете вивчити редукцію та інтегрувати його у свій додаток.

Що таке флюс?

Flux використовує односпрямований шаблон потоку даних для вирішення складності управління станом. Пам’ятайте, що це не рамки - це скоріше модель, яка спрямована на вирішення питання державного управління.

Вам цікаво, що не так із існуючим фреймворком MVC? Уявіть, що програма вашого клієнта збільшується. Ви взаємодієте між багатьма моделями та поглядами. Як би це виглядало?

Співвідношення між компонентами ускладнюється. Масштабувати програму стає важко. Facebook зіткнувся з тією ж проблемою. Щоб вирішити цю проблему, вони створили єдиний спрямований потік даних .

Як видно з малюнка вище, у Flux використовується багато компонентів. Давайте розглянемо всі компоненти по одному.

Перегляд: цей компонент відображає інтерфейс користувача. Всякий раз, коли на ньому відбувається будь-яка взаємодія з користувачем (подібно до події), це запускає дію. Крім того, коли Магазин повідомляє Погляду про те, що відбулися певні зміни, він відтворюється сам. Наприклад, якщо користувач натискає кнопку Додати .

Дія: це обробляє всі події. Ці події передаються компонентом подання. Цей рівень зазвичай використовується для здійснення викликів 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. Легко перевіряється за допомогою модульного тесту.
  3. Можна створювати масштабовані програми.
  4. Передбачуваний потік даних.
Note: The only drawback with the Flux is that there is some boilerplate that we need to write. Besides the boilerplate, there is little code we need to write when adding components to the existing application.

Application Template

To learn how to implement flux in ReactJS, we will build a Posts page. Here we will display all the posts. The application template is available at this commit. We will use this as the template for integrating Flux on top of it.

To clone the code from this commit, use the following command:

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

We will require a react-router-dom and bootstrap module. To install these packages, use the following command:

npm install [email protected] [email protected] 

Once done you'll see the following application:

Щоб детально зрозуміти 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.