كيفية جعل تطبيق React الخاص بك يعمل بكامل طاقته ، ويتفاعل بشكل كامل ، ويكون قادرًا على التعامل مع كل هذه الآثار الجانبية المجنونة

البرمجة التفاعلية الوظيفية (FRP) هي نموذج اكتسب الكثير من الاهتمام مؤخرًا ، خاصة في عالم الواجهة الأمامية لجافا سكريبت. إنه مصطلح مكتظ ، ولكنه يصف فكرة بسيطة:

يجب أن يكون كل شيء نقيًا بحيث يكون من السهل اختباره والسبب حوله (وظيفي) ، ويجب أن يكون سلوك المزامنة على غرار القيم التي تتغير بمرور الوقت (التفاعلية).

التفاعل في حد ذاته ليس وظيفيًا بالكامل ، كما أنه لا يتفاعل بشكل كامل. لكنه مستوحى من بعض المفاهيم الكامنة وراء FRP. المكونات الوظيفية على سبيل المثال هي وظائف نقية فيما يتعلق الدعائم الخاصة بهم. وهم يتفاعلون مع تغييرات الدعامة أو الحالة.

ولكن عندما يتعلق الأمر بمعالجة الآثار الجانبية ، فإن React - كونه طبقة العرض فقط - يحتاج إلى مساعدة من مكتبات أخرى ، مثل Redux.

سأتحدث في هذا المقال عن دورات إعادة التكرار ، وهي أداة وسيطة لـ Redux تساعدك في التعامل مع الآثار الجانبية وتشفير الكود في تطبيقات React بطريقة تفاعلية - وهي سمة لم يتم مشاركتها حتى الآن مع نماذج التأثيرات الجانبية الأخرى - من خلال الاستفادة من إطار Cycle.js.

دورات الإعادة هي في نفس الوقت تفاعلية وتفاعلية

ما هي الآثار الجانبية؟

تأثير جانبي يعدل العالم الخارجي. يعتبر كل شيء في تطبيقك يتعامل مع إنشاء طلبات HTTP أو الكتابة إلى localStorage أو حتى التلاعب بـ DOM ، من الآثار الجانبية.

الآثار الجانبية سيئة. من الصعب اختبارها ومعقدة في صيانتها ، وعمومًا ما تكون معظم الأخطاء فيها. هدفك هو بالتالي تقليل / توطينهم.

مبرمجان بعد توطين الشفرة الفعالة الجانبية (المصدر)
"في وجود آثار جانبية ، يعتمد سلوك البرنامج على التاريخ الماضي ؛ وهذا هو ، ترتيب المسائل التقييم. نظرًا لأن فهم أي برنامج فعال يتطلب التفكير في جميع التواريخ المحتملة ، فغالبًا ما تجعل الآثار الجانبية من الصعب فهم البرنامج. "- نورمان رامزي

فيما يلي عدة طرق شائعة للتعامل مع الآثار الجانبية في Redux:

  1. redux-thunk - يضع رمز الآثار الجانبية داخل منشئي الإجراءات
  2. redux-saga - يجعل من آثارك الجانبية منطقية باستخدام sagas
  3. redux- ملاحظتها - يستخدم البرمجة التفاعلية لنموذج الآثار الجانبية

المشكلة هي أن أيا من هذه هي نقية ورد الفعل على حد سواء. بعضها خالص (ملحمة مسترجعة) بينما البعض الآخر تفاعلي (مسترجع قابل للملاحظة) ، لكن لا أحد منهم يشارك جميع المفاهيم التي قدمناها سابقًا حول FRP.

دورات الإعادة تكون نقية ومتفاعلة.

تحقق من هذه الشرائح الإعادة من قبل نيك باليسترا

سنشرح أولاً بمزيد من التفاصيل هذه المفاهيم الوظيفية والتفاعلية - ولماذا يجب عليك الاهتمام. سنشرح بعد ذلك كيفية عمل دورات الإعادة بالتفصيل.

آثار جانبية نقية التعامل مع Cycle.js

ربما يكون طلب HTTP هو الآثار الجانبية الأكثر شيوعًا. فيما يلي مثال لطلب HTTP باستخدام redux-thunk:

وظيفة جلب المستخدم (مستخدم) {
  العودة (الإرسال ، getState) =>
    جلب ( `https://api.github.com/users/$ {المستخدم}`)
}

هذه الوظيفة ضرورية. نعم ، يُرجع الوعد ويمكنك ربطه مع الوعود الأخرى ، لكن إحضار () يقوم بإجراء مكالمة ، في تلك اللحظة المحددة من الوقت. انها ليست نقية.

وينطبق الشيء نفسه على الإعادة التي يمكن ملاحظتها:

const fetchUserEpic = action $ =>
  العمل $ .ofType (FETCH_USER)
    .mergeMap (action =>
      ajax.getJSON ( `https://api.github.com/users/$ {action.payload}`)
        .MAP (fetchUserFulfilled)
    )؛

ajax.getJSON () يجعل مقتطف الشفرة هذا أمرًا ضروريًا.

لجعل طلب HTTP خالصًا ، يجب ألا تفكر في "تقديم طلب HTTP الآن" ، بل "اسمح لي أن أصف كيف أريد أن يبدو طلب HTTP الخاص بي" ولا تقلق بشأن وقت حدوثه بالفعل أو من يقوم بتقديمه.

في Cycle.js ، هذا هو أساسًا كيفية ترميز كل الأشياء. كل ما تفعله مع الإطار هو إنشاء أوصاف حول ما تريد القيام به. ثم يتم إرسال هذه الأوصاف إلى هذه الأشياء التي تسمى برامج التشغيل (عبر التدفقات التفاعلية) والتي تتولى في الواقع تقديم طلب HTTP:

الوظيفة الرئيسية (المصادر) {
  طلب const $ = xs.of ({
    عنوان url: `https: // api.github.com / users / foo` ،
  })؛
  إرجاع {
    HTTP: طلب $
  }؛
}

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

السحر يحدث بفضل السائقين. يعرف Cycle.js أنه عندما تقوم وظيفتك بإرجاع كائن باستخدام مفتاح HTTP ، فإنها تحتاج إلى معالجة الرسائل التي تتلقاها من هذا التدفق ، وتنفيذ طلب HTTP وفقًا لذلك (عبر برنامج تشغيل HTTP).

تتيح لك برامج التشغيل التعامل مع الآثار الجانبية بطريقة خالصة.

النقطة الأساسية هي أنك لم تتخلص من الآثار الجانبية - لا يزال يجب أن يحدث طلب HTTP - لكنك قمت بترجمته خارج رمز التطبيق الخاص بك.

إن وظائفك أسهل كثيرًا في التفكير ، كما أنها أسهل في الاختبار نظرًا لأنه يمكنك ببساطة اختبار ما إذا كانت وظائفك تنبعث منها الرسائل الصحيحة - لا حاجة للسخرية أو التوقيت المطلوب.

الآثار الجانبية التفاعلية

في الأمثلة السابقة تطرقنا إلى التفاعل. يجب أن تكون هناك طريقة للتواصل مع هؤلاء الذين يطلق عليهم السائقين حول "القيام بالأشياء في العالم الخارجي" وأن يتم إخطارهم حول "الأشياء التي تحدث في العالم الخارجي".

تمثل الملاحظات (تدفقات الملقب) هي التجريد المثالي لهذا النوع من التواصل غير المتزامن.

كلما أردت "القيام بشيء ما" ، تنبعث إلى تيار الإخراج وصفًا لما تريد القيام به. تسمى تدفقات الإخراج هذه المصارف في Cycle.js world.

كلما أردت "أن يتم إخطارك بشيء حدث" ، يمكنك استخدام دفق إدخال (يسمى المصادر) ويمكنك ببساطة تعيين قيم الدفق لمعرفة ما حدث.

هذا يشكل نوعا من حلقة رد الفعل التي تتطلب تفكير مختلف لفهم من التعليمات البرمجية العادية العادية. دعونا نمثل دورة حياة لطلب / استجابة HTTP باستخدام هذا النموذج:

الوظيفة الرئيسية (المصادر) {
  استجابة const $ = sources.HTTP
    . تحديد ( 'فو')
    .flatten ()
    .map (response => response) ؛
  طلب const $ = xs.of ({
    عنوان url: `https: // api.github.com / users / foo` ،
    الفئة: 'فو' ،
  })؛
  المصارف المصارف = {
    HTTP: طلب $
  }؛
  المصارف العودة ؛
}

يعرف برنامج تشغيل HTTP عن مفتاح HTTP الذي يتم إرجاعه بواسطة هذه الوظيفة. إنه دفق يحتوي على وصف طلب HTTP لعنوان GitHub. إنه يخبر برنامج تشغيل HTTP: "أريد تقديم طلب إلى عنوان url هذا".

يعرف برنامج التشغيل بعد ذلك تنفيذ الطلب ، ويرسل الاستجابة مرة أخرى إلى الوظيفة الرئيسية كمصدر (sources.HTTP) - لاحظ أن المصارف والمصادر تستخدم نفس مفتاح الكائن.

دعنا نوضح ذلك مرة أخرى: نحن نستخدم sources.HTTP "لإشعار باستجابات HTTP". ونعود sinks.HTTP إلى "تقديم طلبات HTTP".

لتوضيح هذه الحلقة التفاعلية المهمة ، توجد هنا رسوم متحركة:

حلقة رد الفعل بين التطبيق الخاص بك والعالم الخارجي

هذا يبدو بديهيًا مقارنةً بالبرمجة العادية الضرورية: لماذا توجد شفرة قراءة الاستجابة قبل الكود المسؤول عن الطلب؟

هذا لأنه لا يهم مكان وجود الشفرة في FRP. كل ما عليك فعله هو إرسال الأوصاف ، والاستماع إلى التغييرات. ترتيب الكود غير مهم.

وهذا يسمح لإعادة بناء رمز سهلة للغاية.

تقديم دورات الإعادة

Redux-cycles هي مزيج من Redux و Cycle.js

في هذه المرحلة قد تسأل ، ما علاقة كل هذا بتطبيق React الخاص بي؟

لقد تعرفت على مزايا جعل الشفرة نقية ، وذلك فقط بكتابة أوصاف لما تريد القيام به. لقد تعرفت على مزايا استخدام Observables للتواصل مع العالم الخارجي.

سترى الآن كيفية استخدام هذه المفاهيم داخل تطبيقات React الموجودة لديك ، في الواقع ، تعمل بكامل طاقتها وتتفاعل.

اعتراض وإرسال إجراءات Redux

مع Redux ، يمكنك إرسال إجراءات لإعلام المخفضين أنك تريد حالة جديدة.

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

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

الوظيفة الرئيسية (المصادر) {
  طلب const $ = sources.ACTION
    .filter (action => action.type === FETCH_USER)
    .map (action => ({
      عنوان url: `https://api.github.com/users/$ {action.payload}`،
      الفئة: "المستخدمين" ،
    }))؛

  إجراء العمل $ = sources.HTTP
    . تحديد ( 'المستخدمين)
    .flatten ()
    .MAP (fetchUserFulfilled)؛

  المصارف المصارف = {
    العمل: العمل $ ،
    HTTP: طلب $
  }؛
  المصارف العودة ؛
}

في المثال أعلاه ، يوجد مصدر ومغسلة جديدان مقدمة من دورات الإعادة - ACTION. لكن نموذج التواصل هو نفسه.

يستمع إلى الإجراءات التي يتم إرسالها من العالم Redux باستخدام sources.ACTION. وهو يرسل إجراءات جديدة إلى عالم Redux من خلال إرجاع sinks.ACTION.

على وجه التحديد تنبعث كائنات إجراءات التدفق.

الشيء الرائع هو أنه يمكنك الجمع بين الأشياء التي تحدث من برامج التشغيل الأخرى. في المثال السابق ، تحدث الأشياء التي تحدث في عالم HTTP بالفعل تغييرات في عالم ACTION ، والعكس بالعكس.

- لاحظ أن التواصل مع Redux يحدث بالكامل من خلال مصدر / مصدر ACTION. يتعامل سائقو Redux-cycles مع عمليات الإرسال الفعلية لك.

كيف تتفاعل برامج التشغيل المختلفة مع بعضها البعض

ماذا عن التطبيقات الأكثر تعقيدًا؟

كيف يمكن للمرء تطوير تطبيقات أكثر تعقيدًا إذا كنت تكتب فقط وظائف خالصة تعمل على تحويل تدفقات البيانات؟

تبين أنه يمكنك فعل أي شيء إلى حد كبير باستخدام برامج التشغيل المضمنة بالفعل. أو يمكنك بسهولة بناء سيارتك الخاصة - إليك برنامج تشغيل بسيط يقوم بتسجيل الرسائل المكتوبة على الحوض الخاص به.

المدى (الرئيسي ، {
  LOG: msg $ => msg $ .addListener ({
    التالي: msg => console.log (msg)
  })
})؛

يعد run جزءًا من Cycle.js ، والذي يقوم بتشغيل وظيفتك الرئيسية (الوسيطة الأولى) ويمر عبر جميع برامج التشغيل (الوسيطة الثانية).

تقدم Redux-cycles برنامجي تشغيل يسمحان لك بالتواصل مع Redux ؛ makeActionDriver () و makeStateDriver ():

استيراد {createCycleMiddleware} من "دورات الإعادة" ؛
const cycleMiddleware = createCycleMiddleware ()؛
const {makeActionDriver، makeStateDriver} = cycleMiddleware؛
متجر const = createStore (
  rootReducer،
  applyMiddleware (cycleMiddleware)
)؛
المدى (الرئيسي ، {
  العمل: makeActionDriver () ،
  الحالة: makeStateDriver ()
})

makeStateDriver () هو برنامج تشغيل للقراءة فقط. هذا يعني أنه يمكنك فقط قراءة المصادر. STATE في وظيفتك الرئيسية. لا يمكنك إخبارها بما يجب فعله ؛ يمكنك فقط قراءة البيانات منه.

في كل مرة تتغير حالة Redux ، ستصدر دفق thesources.STATE كائن الحالة الجديد. يكون هذا مفيدًا عندما تحتاج إلى كتابة منطق محدد استنادًا إلى الحالة الحالية للتطبيق.

يتم الاحتفاظ Redux و Cycle.js منفصلة. انهم التواصل فقط عبر السائقين دورات الإعادة.

تدفق البيانات المتزامن معقدة

تأتي الملاحظات من المشغلين ، مما يسمح لك ببناء تدفقات غير متزامنة معقدة

ميزة أخرى كبيرة للبرمجة التفاعلية هي القدرة على استخدام المشغلين لتكوين التدفقات في تدفقات أخرى - بمعاملتها الفعالة كصفيف من القيم مع مرور الوقت: يمكنك تعيينها وتصفيتها وحتى تقليلها.

المشغلين جعل الرسوم البيانية واضحة تدفق البيانات ممكن ؛ أي تعليل التبعيات بين العمليات. مما يتيح لك تصور البيانات التي تتدفق من خلال مختلف المشغلين مثل الرسوم المتحركة أعلاه.

يتيح لك Redux-الملاحظة أيضًا كتابة تدفقات غير متزامنة معقدة - فهي تستخدم مثال WebSocket متعدد كنقطة بيع - ومع ذلك ، فإن قوة كتابة هذه التدفقات بطريقة خالصة هي ما يميز Cycle.js حقًا.

نظرًا لأن كل شيء عبارة عن تدفق بيانات خالص ، يمكننا أن نتخيل مستقبلاً لن تكون البرمجة فيه سوى توصيل مجموعات من العوامل.

اختبار مع المخططات الرخام

رسم رخامي. يمثل كل سهم دفقًا. كل دائرة هي القيمة المنبعثة على هذا الدفق.

أخيرا وليس آخرا يأتي الاختبار. هذا هو المكان الذي تشرق فيه دورات الإعادة (وبشكل عام جميع تطبيقات Cycle.js).

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

باستخدام مشروع @ دورة / وقت رائع ، يمكنك حتى رسم مخططات رخامية واختبار وظائفك بطريقة مرئية للغاية:

assertSourcesSinks ({
  الإجراء: {'-a-b-c ---- |': actionSource} ،
  HTTP: {'--- r ------ |': httpSource} ،
} ، {
  HTTP: {'--------- r |': httpSink} ،
  الإجراء: {'--- a ------ |': actionSink} ،
}، searchUsers، done)؛

تنفذ هذه الشفرة وظيفة searchUsers ، لتمريرها كمصادر محددة كمدخل (الوسيطة الأولى). بالنظر إلى هذه المصادر ، تتوقع الدالة أن تُرجع الأحواض المتوفرة (الوسيطة الثانية). إذا لم يحدث ذلك ، فشل التأكيد.

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

عندما يصدر مصدر HTTP r (استجابة) ، تتوقع على الفور ظهور (إجراء) في مصدر ACTION - يحدث في نفس الوقت. ومع ذلك ، عندما يصدر مصدر ACTION مجموعة من -a-b-c ، لا تتوقع ظهور أي شيء في تلك اللحظة في مصدر HTTP.

وذلك لأن searchUsers يهدف إلى رفض الإجراءات التي يتلقاها. لن يتم إرسال طلب HTTP إلا بعد 800 مللي ثانية من عدم النشاط على دفق مصدر ACTION: إنه ينفذ وظيفة الإكمال التلقائي.

اختبار هذا النوع من السلوك غير المتزامن هو تافه مع وظائف نقية ورد الفعل.

استنتاج

في هذه المقالة أوضحنا القوة الحقيقية لـ FRP. قدمنا ​​Cycle.js ونماذجها الرواية. تعتبر قائمة Cycle.js الرائعة موردا هاما إذا كنت تريد معرفة المزيد عن هذه التكنولوجيا.

يتطلب استخدام Cycle.js بمفرده - بدون React أو Redux - قليلاً من التبديل في العقلية ولكن يمكنك القيام به إذا كنت على استعداد للتخلي عن بعض التقنيات والموارد في مجتمع React / Redux.

تتيح لك دورات الإعادة من ناحية أخرى الاستمرار في استخدام كل الأشياء التفاعلية React أثناء تبليل يديك باستخدام FRP و Cycle.js.

شكر خاص لـ Gosha Arinich و Nick Balestra لاستمرارهما في المشروع مع نفسي ، وإلى Nick Johnstone لإثبات قراءة هذا المقال.