كيفية استخدام Flux لإدارة الحالة في ReactJS - شرح بمثال

إذا كنت قد بدأت العمل على ReactJS مؤخرًا ، فقد تتساءل عن كيفية إدارة الحالة في React بحيث يمكن للتطبيق الخاص بك التوسع.

لحل مشكلة إدارة الدولة هذه ، طورت العديد من الشركات والأفراد حلولًا مختلفة. توصل Facebook ، الذي طور ReactJS ، إلى حل يسمى Flux .

كنت قد سمعت عن مسترجع إذا كنت قد عملت على التكنولوجيا الأمامية مثل 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" } }

يعد مفتاح نوع الإجراء إلزاميًا ويستخدمه المرسل لتمرير التحديثات إلى المتجر ذي الصلة. من الممارسات المعروفة أيضًا استخدام الثوابت للاحتفاظ باسم القيمة لمفتاح نوع الإجراء حتى لا تحدث أخطاء مطبعية. تحتفظ البيانات بمعلومات الحدث التي نريد إرسالها من Action إلى Store. يمكن أن يكون اسم هذا المفتاح أي شيء.

المرسل: هذا هو المحور المركزي والتسجيل الفردي. يرسل الحمولة من الإجراءات إلى المتجر. تأكد أيضًا من عدم وجود تأثيرات متتالية عند إرسال إجراء إلى المتجر. إنه يضمن عدم حدوث أي إجراء آخر قبل أن تكمل طبقة البيانات عمليات المعالجة والتخزين.

ضع في اعتبارك أن هذا المكون يحتوي على وحدة تحكم في حركة المرور في النظام. إنها قائمة مركزية لعمليات الاسترجاعات. يستدعي رد الاتصال ويبث الحمولة التي تلقاها من الإجراء.

بسبب هذا المكون ، يمكن التنبؤ بتدفق البيانات. يقوم كل إجراء بتحديث المتجر المحدد باستخدام رد الاتصال المسجل لدى المرسل.

المتجر: هذا يحمل حالة التطبيق وهو طبقة بيانات لهذا النمط. لا تعتبره نموذجًا من MVC. يمكن أن يحتوي التطبيق على متجر تطبيقات واحد أو أكثر. يتم تحديث المتاجر لأن لديهم رد اتصال مسجل باستخدام المرسل.

يتم استخدام باعث الأحداث في Node لتحديث المتجر وبث التحديث لعرضه. لا يقوم العرض أبدًا بتحديث حالة التطبيق بشكل مباشر. يتم تحديثه بسبب التغييرات التي طرأت على المتجر.

هذا جزء فقط من Flux يمكنه تحديث البيانات. الواجهات المنفذة بالمحل كالتالي:

  1. و EventEmitter تم تمديده لإطلاع الرأي القائل بأن تم تحديث تخزين البيانات.
  2. تتم إضافة المستمعين مثل addChangeListener و removeChangeListener .
  3. emitChange يستخدم لإصدار التغيير.

ضع في اعتبارك الرسم البياني أعلاه مع المزيد من المتاجر وجهات النظر. مع ذلك ، سيكون نمط وتدفق البيانات متماثلين. هذا لأن هذا اتجاه واحد وتدفق بيانات يمكن التنبؤ به ، على عكس MVC أو الربط ثنائي الاتجاه. هذا يحسن من اتساق البيانات وانها أسهل للعثور على الأخطاء .

حسنًا ، يجلب Flux الفوائد الرئيسية التالية إلى الجدول بمساعدة تدفق البيانات أحادي الاتجاه:

  1. يصبح الرمز واضحًا تمامًا وسهل الفهم.
  2. قابل للاختبار بسهولة باستخدام اختبار الوحدة.
  3. يمكن بناء تطبيقات قابلة للتطوير.
  4. تدفق البيانات المتوقع.
ملاحظة: العيب الوحيد في 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  ملف هنا . هذا ملف بيانات وهمي. نظرًا لأننا لا نريد إنشاء واجهات برمجة التطبيقات والتركيز بدلاً من ذلك على Flux ، فسنقوم باسترداد البيانات من هذا الملف.

المكون الأساسي لتطبيقنا هو index.js. هنا قمنا بتصيير الدليل App.jsالداخلي index.htmlunder public باستخدام التابعين render و getElementById . و App.jsيستخدم لتكوين الطرق.

نقوم أيضًا بإضافة مكون NavBar في الجزء العلوي من الآخر بحيث يكون متاحًا لجميع المكونات.

داخل صفحات دليل لدينا 3 ملفات => Home.js، Posts.jsو NotFound.js. Home.js  يتم استخدامه ببساطة لعرض مكون الصفحة الرئيسية. عندما يقوم المستخدم بالتوجيه إلى عنوان URL غير موجود ، يتم NotFound.jsعرضه.

و Posts.jsهو العنصر الأصل ويتم استخدامه للحصول على البيانات من db.jsonملف. يمر هذه البيانات إلى PostLists.jsتحت مكونات الدليل. هذا المكون هو مكون غبي ويتعامل فقط مع واجهة المستخدم. يحصل على البيانات كدعامات من المكون الأصلي ( Posts.js) ويعرضها في شكل بطاقات.

الآن بعد أن أصبحنا واضحين بشأن كيفية عمل تطبيق المدونة الخاص بنا ، سنبدأ بدمج Flux فوقه.

دمج الجريان

قم بتثبيت Flux باستخدام الأمر التالي:

npm install [email protected]

لدمج Flux في تطبيقنا ، سنقسم هذا القسم إلى 4 أقسام فرعية:

  1. المرسل
  2. أجراءات
  3. المتاجر
  4. رأي

ملاحظة: الكود الكامل متاح في هذا المستودع.

المرسل

أولا، إنشاء مجلدين جديد يسمى الإجراءات و مخازن تحت SRC الدليل. بعد ذلك قم بإنشاء ملف باسم appDispatcher.js  نفس دليل src.

ملحوظة: من الآن ، ستحتوي جميع الملفات المرتبطة بـ Flux على غلاف Camel لأنها ليست مكونات ReactJS.

انتقل إلى appDispatcher.jsولصق الكود التالي:

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 ، فإننا نقرأها في الإجراءات ، ونخزنها في المتجر ، ونرسلها إلى المكونات التي تتطلبها.

عند استخدام واجهة برمجة التطبيقات الفعلية ، ستجد أن التطبيق يقوم بتحميل البيانات من واجهة برمجة التطبيقات مرة واحدة ويخزنها في المتجر. عندما نعيد زيارة الصفحة نفسها ، ستلاحظ أنه لا يلزم استدعاء واجهة برمجة التطبيقات مرة أخرى. يمكنك مراقبته ضمن علامة التبويب المصدر في وحدة تحكم مطوري Chrome.

وانتهينا !! آمل أن يكون هذا البرنامج التعليمي قد جعل فكرة Flux أكثر وضوحًا وستتمكن من استخدامها في مشاريعك.

لا تتردد في التواصل معي على Twitter و Github.