Mrr هي مكتبة تفاعلية وظيفية لـ React (أعتذر عن الحشو التخيلي).
تشير كلمة "تفاعلية" عادةً
إلى Rx.js كمرجع FRP. ومع ذلك ، أظهرت سلسلة من المقالات الحديثة حول هذا الموضوع عن حبري (
[1] ،
[2] ،
[3] ) إرهاق حلول Rx ، التي فقدت في الأمثلة البسيطة الوضوح والبساطة لأي نهج آخر تقريبًا. Rx كبير وقوي ، وهو مثالي لحل المشكلات التي يقترح فيها تجريد التدفق نفسه (في الممارسة ، هذا هو تنسيق المهام غير المتزامنة). ولكن هل تكتب ، على سبيل المثال ، التحقق البسيط من الشكل المتزامن على Rx؟ هل سيوفر وقتك مقارنة بالنهج الحتمية المعتادة؟
mrr هو محاولة لإثبات أن FRF يمكن أن يكون حلاً مناسبًا وفعالًا ليس فقط في مشاكل "التدفق" المحددة ، ولكن أيضًا في المهام الأمامية الروتينية الأكثر شيوعًا.
تعد البرمجة التفاعلية تجريدًا قويًا للغاية ، وهي موجودة حاليًا في الواجهة الأمامية بطريقتين:
- المتغيرات التفاعلية (المتغيرات المحسوبة): بسيطة وموثوقة وبديهية ، ولكن إمكانات RP أبعد ما تكون عن الكشف الكامل
- مكتبات للعمل مع التدفقات ، مثل Rx ، وبيكون ، وما إلى ذلك: قوية ، ولكنها معقدة نوعًا ما ، يقتصر نطاق الاستخدام العملي على مهام محددة.
يجمع Mrr بين مزايا هذه الأساليب. على عكس Rx.js ، يحتوي mrr على واجهة برمجة تطبيقات قصيرة ، يمكن للمستخدم توسيعها بإضافاته. بدلاً من العشرات من الأساليب والمشغلين - أربعة عوامل أساسية ، بدلاً من الملاحظة (الساخنة والباردة) ، الموضوع ، إلخ. - تجريد واحد: تيار. أيضا ، يفتقر السيد mrr إلى بعض المفاهيم المعقدة التي يمكن أن تعقد بشكل كبير إمكانية قراءة التعليمات البرمجية ، على سبيل المثال ، النقائل.
ومع ذلك ، فإن السيد ليس "Rx مبسط بطريقة جديدة." استنادًا إلى نفس المبادئ الأساسية مثل Rx ، يدعي mrr أنه مكانة أكبر: إدارة حالة التطبيق العالمية والمحلية (على مستوى المكون). على الرغم من أن المفهوم الأصلي للبرمجة التفاعلية كان يهدف إلى العمل مع المهام غير المتزامنة ، فقد استخدم السيد mrr بنجاح مناهج التفاعل للمهام العادية والمتزامنة. هذا هو مبدأ "FRP الكلي".
غالبًا عند إنشاء تطبيق على React ، يتم استخدام العديد من التقنيات المختلفة: إعادة التركيب (أو قريباً - السنانير) لحالة المكون ، Redux / mobx للحالة العالمية ، Rx مع redux-ملحوظ (أو thunk / saga) لإدارة الآثار الجانبية والتنسيق غير المتزامن المهام في المحرر. بدلاً من مثل هذه "السلطة" من الأساليب والتقنيات المختلفة داخل نفس التطبيق ، مع السيد يمكنك استخدام تقنية ونموذج واحد.
تختلف واجهة mrr أيضًا بشكل كبير عن Rx والمكتبات المماثلة - فهي أكثر وضوحًا. بفضل تجريد التفاعل والنهج الإعلاني ، يسمح لك السيد بكتابة كود معبر وموجز. على سبيل المثال ، يأخذ
TodoMVC القياسي على mrr أقل من 50 سطرًا من التعليمات البرمجية (لا يحسب قالب JSX).
ولكن ما يكفي من الدعاية. هل تمكنت من الجمع بين مزايا RP "الخفيف" و "الثقيل" في زجاجة واحدة - يجب أن تحكم ، ولكن أولاً ، يرجى قراءة أمثلة التعليمات البرمجية.
TodoMVC مؤلم بالفعل ، ومثال تنزيل البيانات حول مستخدمي Github بدائي للغاية بحيث لا يمكن الشعور بميزات المكتبة عليه. سننظر إلى السيد السيد كمثال على تطبيق مشروط لشراء تذاكر القطار. في واجهة المستخدم لدينا ستكون هناك حقول لاختيار محطات البدء والتاريخ والتواريخ. بعد ذلك ، بعد إرسال البيانات ، سيتم إرجاع قائمة بالقطارات والأماكن المتاحة فيها. بعد تحديد قطار معين ونوع السيارة ، سيدخل المستخدم بيانات الركاب ، ثم يضيف التذاكر إلى السلة. دعنا نذهب.
نحتاج إلى نموذج مع اختيار المحطات والتواريخ:

إنشاء حقول الإكمال التلقائي لدخول المحطات.
import { withMrr } from 'mrr'; const stations = [ '', '', '', ' ', ... ] const Tickets = withMrr({
يتم إنشاء مكونات mrr باستخدام الدالة withMrr ، والتي تقبل مخطط الارتباط التفاعلي (وصف التدفقات) ووظيفة التجسيد. يتم تمرير وظائف التقديم إلى الدعائم للمكون ، وكذلك الحالة ، والتي يتم التحكم فيها الآن بالكامل بواسطة السيد. سيحتوي على الأولي (block $ init) ويحسب بقيم الصيغة للخلايا التفاعلية.
الآن لدينا
خليتان ( أو
دفقان ، وهو نفس الشيء):
stationFromInput ، والقيم التي
تنبثق من إدخال المستخدم باستخدام
$ helper (تمرير
event.target.value افتراضيًا لعناصر إدخال البيانات) ، وخلية مشتقة منه
stationFromOptions ، تحتوي على مصفوفة من المحطات المطابقة بالاسم.
يتم حساب قيمة
stationFromOptions تلقائيًا في كل مرة يتم فيها تغيير خلية رئيسية باستخدام دالة (في مصطلحات mrr تسمى "
الصيغة " - عن طريق القياس باستخدام صيغ Excel). إن بنية تعبيرات mrr بسيطة: في المقام الأول هي الوظيفة (أو عامل التشغيل) التي يتم من خلالها حساب قيمة الخلية ، ثم هناك قائمة بالخلايا التي تعتمد عليها هذه الخلية: يتم تمرير قيمها إلى الدالة. مثل هذا الغريب ، للوهلة الأولى ، يحتوي بناء الجملة على العديد من المزايا ، والتي سننظر فيها لاحقًا. حتى الآن ، يذكرنا منطق السيد هنا بالنهج المعتاد مع المتغيرات الحسابية المستخدمة في Vue و Svelte والمكتبات الأخرى ، والفرق الوحيد هو أنه يمكنك استخدام الوظائف النقية.
نقوم بتنفيذ استبدال المحطة المختارة من القائمة في حقل الإدخال. من الضروري أيضًا إخفاء قائمة المحطات بعد أن ينقر المستخدم على إحدى المحطات.
const Tickets = withMrr({ $init: { stationFromOptions: [], stationFromInput: '', }, stationFromOptions: [str => stations.filter(s => str.indexOf(a) === 0), 'stationFromInput'], stationFrom: ['merge', 'stationFromInput', 'selectStationFrom'], optionsShown: ['toggle', 'stationFromInput', 'selectStationFrom'], }, (state, props, $) => { return (<div> <div> : <input onChange={ $('stationFromInput') } value={ state.stationFrom }/> </div> { state.optionsShown && <ul className="stationFromOptions"> { state.stationFromOptions.map(s => <li onClick={ $('selectStationFrom', s) }>{ s }</li>) } </ul> } </div>); });
سيقوم معالج الأحداث الذي تم إنشاؤه باستخدام $ helper في قائمة المحطات بإصدار قيم ثابتة لكل خيار.
mrr ثابت في نهجها الإعلاني ، غريب على أي طفرات. بعد اختيار المحطة ، لا يمكننا "فرض" تغيير قيمة الخلية. بدلاً من ذلك ، ننشئ محطة جديدة من الخلية ، والتي ، باستخدام عامل دمج الدمج (المكافئ التقريبي في Rx مدمج آخر) ، ستجمع قيم
دفقين : إدخال المستخدم (
stationFromInput ) واختيار المحطة (
selectStationFrom ).
يجب أن نعرض قائمة الخيارات بعد أن يدخل المستخدم شيئًا ما ، ويخفي بعد أن يختار أحد الخيارات.
ستكون خلية
الخيارات المعروضة مسؤولة عن رؤية قائمة الخيارات ، والتي ستأخذ قيم منطقية اعتمادًا على التغييرات في الخلايا الأخرى. هذا نمط شائع جدًا يوجد به السكر النحوي - عامل
التبديل . تقوم بتعيين قيمة الخلية إلى
true عند أي تغيير في الوسيطة الأولى (الدفق) ، وإلى
false - الثانية.
أضف زرًا لمسح النص الذي تم إدخاله.
const Tickets = withMrr({ $init: { stationFromOptions: [], stationFromInput: '', }, stationFromOptions: [str => stations.filter(s => str.indexOf(a) === 0), 'stationFromInput'], clearVal: [a => '', 'clear'], stationFrom: ['merge', 'stationFromInput', 'selectStationFrom', 'clearVal'], optionsShown: ['toggle', 'stationFromInput', 'selectStationFrom'], }, (state, props, $) => { return (<div> <div> : <input onChange={ $('stationFromInput') } value={ state.stationFrom }/> { state.stationFrom && <button onClick={ $('clear') }></button> } </div> { state.optionsShown && <ul className="stationFromOptions"> { state.stationFromOptions.map(s => <li onClick={ $('selectStationFrom', s) }>{ s }</li>) } </ul> } </div>); });
الآن تقوم
stationstation من الخلية ، المسؤولة عن محتوى النص في حقل الإدخال ، بجمع قيمها ليس من اثنين ، ولكن من ثلاثة تيارات. يمكن تبسيط هذا الرمز. يشبه بناء mrr للنموذج [* الصيغة * ، * ... وسائط الخلية *] تعبيرات S في Lisp ، وكما هو الحال في Lisp ، يمكنك تداخل هذه الإنشاءات بشكل تعسفي في بعضها البعض.
دعونا نتخلص من خلية clearVal عديمة الفائدة وتقصير الرمز:
stationFrom: ['merge', 'stationFromInput', 'selectStationFrom', [a => '', 'clear']],
يمكن مقارنة البرامج المكتوبة بأسلوب حتمي مع فريق ضعيف التنظيم ، حيث يطلب الجميع باستمرار شيئًا إلى بعضهم البعض (ألمح إلى استدعاءات الأسلوب وتغيير التغييرات) ، علاوة على ذلك ، كلا المديرين مرؤوسان ، والعكس صحيح. تشبه البرامج التصريحية الصورة المثالية المعاكسة: مجموعة حيث يعرف الجميع بوضوح كيف يجب أن يتصرف في أي موقف. ليست هناك حاجة لأوامر في مثل هذا الفريق ؛ فالجميع في أماكنهم ويعملون استجابة لما يحدث.
بدلاً من وصف جميع العواقب المحتملة لحدث ما (اقرأ - لإجراء طفرات معينة) ، فإننا نصف جميع الحالات التي يمكن أن يحدث فيها هذا الحدث ، أي ما هي القيمة التي ستأخذها الخلية عند أي تغييرات في الخلايا الأخرى. في مثالنا الصغير حتى الآن ، قمنا بوصف المحطة من الخلية وثلاث حالات تؤثر على قيمتها. بالنسبة لمبرمج معتاد على الكود الحتمي ، قد يبدو هذا النهج غير عادي (أو حتى "عكاز" ، أو "انحراف"). في الواقع ، يتيح لك توفير الجهد نظرًا لإيجاز (واستقرار) الشفرة ، كما سنرى في الممارسة.
ماذا عن التزامن؟ هل من الممكن سحب قائمة المحطات المقترحة بأجاكس؟ لا مشكلة! في الجوهر ، لا يهم mrr سواء أعادت الدالة قيمة أو وعد. عند إرجاع mrr ، سينتظر حلها و "دفع" البيانات المستلمة في الدفق.
stationFromOptions: [str => fetch('/get_stations?str=' + str).then(res => res.toJSON()), 'stationFromInput'],
هذا يعني أيضًا أنه يمكنك استخدام الدالات غير المتزامنة كصيغ. سيتم النظر في الحالات الأكثر تعقيدًا (معالجة الأخطاء ، وحالة الوعد) لاحقًا.
وظيفة اختيار محطة المغادرة جاهزة. لا معنى لتكرار نفس الشيء بالنسبة لمحطة الوصول ، يجدر وضعه في مكون منفصل يمكن إعادة استخدامه. سيكون هذا مكون إدخال عام مع الإكمال التلقائي ، لذلك سنعيد تسمية الحقول ونجعل الوظيفة للحصول على خيارات مناسبة محددة في الدعائم.
const OptionsInput = withMrr(props => ({ $init: { options: [], }, val: ['merge', 'valInput', 'selectOption', [a => '', 'clear']], options: [props.getOptions, 'val'], optionsShown: ['toggle', 'valInput', 'selectOption'], }), (state, props, $) => <div> <div> <input onChange={ $('valInput') } value={ state.val } /> </div> { state.optionsShown && <ul className="options"> { state.options.map(s => <li onClick={ $('selectOption', s) }>{ s }</li>) } </ul> } { state.val && <div className="clear" onClick={ $('clear') }> X </div> } </div>)
كما ترى ، يمكنك تحديد بنية خلايا mrr كدالة لمكون الدعائم (ومع ذلك ، سيتم تنفيذها مرة واحدة فقط - عند التهيئة ، ولن تستجيب للتغيرات في الدعائم).
التواصل بين المكونات
الآن قم بتوصيل هذا المكون في المكون الأصلي وانظر كيف يسمح السيد mrr للمكونات ذات الصلة بتبادل البيانات.
const getMatchedStations = str => fetch('/get_stations?str=' + str).then(res => res.toJSON()); const Tickets = withMrr({ stationTo: 'selectStationFrom/val', stationFrom: 'selectStationTo/val', }, (state, props, $, connectAs) => { return (<div> <OptionsInput { ...connectAs('selectStationFrom') } getOptions={ getMatchedStations } /> - <OptionsInput { ...connectAs('selectStationTo') } getOptions={ getMatchedStations } /> <input type="date" onChange={ $('date') } /> <button onClick={ $('searchTrains') }></button> </div>); });
لربط مكون أصل
بمكون تابع ، يجب علينا تمرير المعلمات إليه باستخدام دالة
connectAs (الوسيطة الرابعة لوظيفة التجسيد). في هذه الحالة ، نشير إلى الاسم الذي نريد أن نمنحه للمكون الفرعي. من خلال إرفاق مكون بهذه الطريقة ، يمكننا بهذا الاسم الوصول إلى خلاياه. في هذه الحالة ، نستمع إلى خلايا فال. والعكس ممكن أيضًا - استمع من المكون الفرعي للخلية الأم.
كما ترون ، هنا mrr يتبع نهجًا إعلانيًا: لا حاجة لاسترجاعات onChange ، نحتاج فقط إلى تحديد اسم للمكون الفرعي في وظيفة connectAs ، وبعد ذلك نحصل على خلاياه! في هذه الحالة ، مرة أخرى بسبب التصريح ، لا يوجد تهديد بالتدخل في عمل مكون آخر - ليس لدينا القدرة على "تغيير" أي شيء فيه ، والتحول ، لا يمكننا سوى "الاستماع" إلى البيانات.
الإشارات والقيم
المرحلة التالية هي البحث عن القطارات المناسبة للمعلمات المختارة. في النهج الحتمي ، ربما نكتب معالجًا معينًا لإرسال نموذج onSubmit ، والذي سيبدأ المزيد من الإجراءات - طلب ajax وعرض النتائج. ولكن ، كما تتذكر ، لا يمكننا "طلب" أي شيء! يمكننا فقط إنشاء مجموعة أخرى من الخلايا المشتقة من خلايا النموذج. لنكتب طلبًا آخر.
const getTrains = (from, to, date) => fetch('/get_trains?from=' + from + '&to=' + to + '&date=' + date).then(res => res.toJSON()); const Tickets = withMrr({ stationFrom: 'selectStationFrom/val', stationTo: 'selectStationTo/val', results: [getTrains, 'stationFrom', 'stationTo', 'date', 'searchTrains'], }, (state, props, $, connectAs) => { return (<div> <OptionsInput { ...connectAs('selectStationFrom') } getOptions={ getMatchedStations } /> - <OptionsInput { ...connectAs('selectStationTo') } getOptions={ getMatchedStations } /> <input type="date" onChange={ $('date') } /> <button onClick={ $('searchTrains') }></button> </div>); });
ومع ذلك ، لن يعمل هذا الرمز كما هو متوقع. يبدأ الحساب (إعادة حساب قيمة الخلية) عندما يتغير أي من الوسيطات ، لذلك سيتم إرسال الطلب ، على سبيل المثال ، مباشرة بعد اختيار المحطة الأولى ، وليس فقط بالنقر على "بحث". نحتاج ، من ناحية ، إلى تمرير المحطات والتاريخ في حجج الصيغة ، ولكن من ناحية أخرى ، لا نستجيب لتغييرها. في mrr هناك آلية أنيقة لهذا الاستماع السلبي.
results: [getTrains, '-stationFrom', '-stationTo', '-date', 'searchTrains'],
ما عليك سوى إضافة ناقص أمام اسم الخلية وفويلا! الآن ، سوف تستجيب
النتائج فقط للتغييرات في خلية
searchTrains .
في هذه الحالة ،
تعمل خلية
searchTrains بمثابة "إشارة خلوية" ، وخلايا
stationFrom وغيرها ك "قيم خلوية". بالنسبة لخلية الإشارة ، فقط اللحظة التي تكون فيها القيم "تتدفق" عبرها مهمة ، ونوع البيانات التي ستكون عليها - على أي حال: يمكن أن يكون صحيحًا ، "1" أو أيا كان (في حالتنا ، ستكون هذه كائنات حدث DOM ) بالنسبة لخلية القيمة ، فإن قيمتها هي المهمة ، ولكن في الوقت نفسه ، فإن لحظات تغييرها ليست كبيرة. هذان النوعان من الخلايا لا يستبعد أحدهما الآخر: العديد من الخلايا هي إشارات وقيم. على مستوى النحو في mrr ، لا يختلف هذان النوعان من الخلايا بأي شكل من الأشكال ، ولكن الفهم المفاهيمي لمثل هذا الاختلاف مهم جدًا عند كتابة التعليمات البرمجية التفاعلية.
تيارات الانقسام
قد يستغرق طلب البحث عن مقاعد في القطار بعض الوقت ، لذلك يجب علينا عرض اللودر ، والرد أيضًا في حالة حدوث خطأ. هناك بالفعل عدد قليل من الوعود لهذا النهج الافتراضي مع الحل التلقائي.
const Tickets = withMrr({ $init: { results: {}, } stationFrom: 'selectStationFrom/val', stationTo: 'selectStationTo/val', searchQuery: [(from, to, date) => ({ from, to, date }), '-stationFrom', '-stationTo', '-date', 'searchTrains'], results: ['nested', (cb, query) => { cb({ loading: true, error: null, data: null }); getTrains(query.from, query.to, query.date) .then(res => cb('data', res)) .catch(err => cb('error', err)) .finally(() => cb('loading', false)) }, 'searchQuery'], availableTrains: 'results.data', }, (state, props, $, connectAs) => { return (<div> <div> <OptionsInput { ...connectAs('selectStationFrom') } getOptions={ getMatchedStations } /> - <OptionsInput { ...connectAs('selectStationTo') } getOptions={ getMatchedStations } /> <input type="date" onChange={ $('date') } /> <button onClick={ $('searchTrains') }></button> </div> <div> { state.results.loading && <div className="loading">...</div> } { state.results.error && <div className="error"> . , . .</div> } { state.availableTrains && <div className="results"> { state.availableTrains.map((train) => <div />) } </div> } </div> </div>); });
يسمح لك عامل التشغيل
المتداخل "بتحليل" البيانات إلى خلايا فرعية ؛ لهذا ، فإن الحجة الأولى للصيغة هي رد الاتصال ، حيث يمكنك "دفع" البيانات إلى الخلية الفرعية (واحدة أو أكثر). الآن لدينا تدفقات منفصلة مسؤولة عن الخطأ وحالة الوعد والبيانات الواردة. عامل التشغيل المتداخل هو أداة قوية للغاية وواحد من الضرورات القليلة في mrr (نحدد أنفسنا في أي الخلايا لوضع البيانات). بينما يقوم عامل دمج الدمج بدمج سلاسل رسائل متعددة في سلسلة واحدة ، يقوم القسم المتداخل بتقسيم مؤشر الترابط إلى سلاسل فرعية متعددة ، وبالتالي يكون العكس.
المثال أعلاه هو طريقة قياسية للعمل مع الوعود ، في mrr يتم تعميمه كعامل تشغيل للوعد ويسمح لك بتقصير الرمز:
results: ['promise', (query) => getTrains(query.from, query.to, query.date), 'searchQuery'],
أيضا ، يضمن عامل الوعد أن يتم استخدام نتائج أحدث وعد فقط.

مكون لعرض المقاعد المتاحة (للبساطة سنرفض من أنواع مختلفة من السيارات)
const TrainSeats = withMrr({ selectSeats: [(seatsNumber, { id }) => new Array(Number(seatsNumber)).fill(true).map(() => ({ trainId: id })), '-seatsNumber', '-$props', 'select'], seatsNumber: [() => 0, 'selectSeats'], }, (state, props, $) => <div className="train"> №{ props.num } { props.from } - { props.to }. : { props.seats || 0 } { props.seats && <div> : <input type="number" onChange={ $('seatsNumber') } value={ state.seatsNumber || 0 } max={ props.seats } /> <button onClick={ $('select') }></button> </div> } </div>);
للوصول إلى الدعائم في صيغة ، يمكنك الاشتراك في مربع $ props الخاص.
const Tickets = withMrr({ ... selectedSeats: '*/selectSeats', }, (state, props, $, connectAs) => { ... <div className="results"> { state.availableTrains.map((train, i) => <TrainSeats key={i} {...train} {...connectAs('train' + i)}/>) } </div> }
نستخدم مرة أخرى الاستماع السلبي لالتقاط عدد الأماكن المحددة عند النقر فوق الزر "تحديد". نقوم بربط كل مكون فرعي مع الوالد باستخدام وظيفة connectAs. يمكن للمستخدم تحديد المقاعد في أي من القطارات المقترحة ، لذلك نستمع إلى التغييرات في جميع المكونات الفرعية باستخدام القناع "*".
ولكن هنا تكمن المشكلة: يمكن للمستخدم إضافة مقاعد أولاً في قطار واحد ، ثم في قطار آخر ، حتى تطحن البيانات الجديدة تلك السابقة. كيفية "تجميع" دفق البيانات؟ للقيام بذلك ، هناك عامل
إغلاق ، والذي ، مع التداخل والقمع ، يشكل أساس mrr (جميع الآخرين ليسوا أكثر من السكر النحوي على أساس هؤلاء الثلاثة).
selectedSeats: ['closure', () => { let seats = [];
عند استخدام
الإغلاق أولاً (على ComponDDMount) ، يتم إنشاء إغلاق يُرجع الصيغة. وبالتالي ، يمكنها الوصول إلى متغيرات الإغلاق. يتيح لك هذا حفظ البيانات بين المكالمات بطريقة آمنة - دون الانزلاق في هاوية المتغيرات العالمية والحالة المشتركة القابلة للتغيير. وبالتالي ، يتيح لك الإغلاق تنفيذ وظائف عوامل Rx مثل المسح الضوئي وغيرها. ومع ذلك ، هذه الطريقة جيدة للحالات الصعبة. إذا كنا بحاجة فقط إلى حفظ قيمة متغير واحد ، فيمكننا ببساطة استخدام الرابط إلى القيمة السابقة للخلية باستخدام الاسم الخاص "^":
selectedSeats: [(seats, prev) => [...seats, ...prev], '*/selectSeats', '^']
الآن يجب على المستخدم إدخال الاسم الأول والأخير لكل تذكرة مختارة.
const SeatDetails = withMrr({}, (state, props, $) => { return (<div> { props.trainId } <input name="name" value={ props.name } onChange={ $('setDetails', e => ['name', e.target.value, props.i]) } /> <input name="surname" value={ props.surname } onChange={ $('setDetails', e => ['surname', e.target.value, props.i]) }/> <a href="#" onClick={ $('removeSeat', props.i) }>X</a> </div>); }) const Tickets = withMrr({ $init: { results: {}, selectedSeats: [], } stationFrom: 'selectStationFrom/val', stationTo: 'selectStationTo/val', searchQuery: [(from, to, date) => ({ from, to, date }), '-stationFrom', '-stationTo', '-date', 'searchTrains'], results: ['promise', (query) => getTrains(query.from, query.to, query.date), 'searchQuery'], availableTrains: 'results.data', selectedSeats: [(seats, prev) => [...seats, ...prev], '*/selectSeats', '^'] }, (state, props, $, connectAs) => { return (<div> <div> <OptionsInput { ...connectAs('selectStationFrom') } getOptions={ getMatchedStations } /> - <OptionsInput { ...connectAs('selectStationTo') } getOptions={ getMatchedStations } /> <input type="date" onChange={ $('date') } /> <button onClick={ $('searchTrains') }></button> </div> <div> { state.results.loading && <div className="loading">...</div> } { state.results.error && <div className="error"> . , . .</div> } { state.availableTrains && <div className="results"> { state.availableTrains.map((train, i) => <TrainSeats key={i} {...train} {...connectAs('train' + i)}/>) } </div> } { state.selectedSeats.map((seat, i) => <SeatDetails key={i} i={i} { ...seat } {...connectAs('seat' + i)}/>) } </div> </div>); });
تحتوي خلية المقاعد
المحددة على مجموعة من المواقع المحددة. عندما يقوم المستخدم بإدخال الاسم واللقب لكل تذكرة ، يجب علينا تغيير البيانات في العناصر المقابلة للصفيف.
selectedSeats: [(seats, details, prev) => {
النهج القياسي غير مناسب لنا: في الصيغة ، نحتاج إلى معرفة الخلية التي تغيرت والاستجابة وفقًا لذلك. سيساعدنا أحد أشكال عامل الدمج.
selectedSeats: ['merge', { '*/selectSeats': (seats, prev) => { return [...prev, ...seats]; }, '*/setDetails': ([field, value, i], prev) => { prev[i][field] = value; return prev; }, '*/removeSeat': (i, prev) => { prev.splice(i, 1); return prev; }, }, '^'],
هذا يشبه إلى حد ما مخفضات Redux ، ولكن مع بناء أكثر مرونة وقوة. ولا يمكنك أن تخاف من تحوير المصفوفة ، لأن صيغة خلية واحدة فقط هي التي تتحكم فيها ، على التوالي ، يتم استبعاد التغييرات المتوازية (ولكن المصفوفات المتحولة التي يتم تمريرها كوسيطة لا تستحق ذلك بالتأكيد).
مجموعات رد الفعل
النمط عندما تقوم الخلية بتخزين الصفيف وتغييره شائع جدًا. جميع عمليات الصفيف من ثلاثة أنواع: إدراج وتعديل وحذف. لوصف هذا ، هناك عامل
كول أنيق. استخدمها لتبسيط حساب المقاعد
المختارة .
كان:
selectedSeats: ['merge', { '*/selectSeats': (seats, prev) => { return [...prev, ...seats]; }, '*/setDetails': ([field, value, i], prev) => { prev[i][field] = value; return prev; }, '*/removeSeat': (i, prev) => { prev.splice(i, 1); return prev; }, 'addToCart': () => [], }, '^']
أصبح:
selectedSeats: ['coll', { create: '*/selectSeats', update: '*/setDetails', delete: ['merge', '*/removeSeat', [() => ({}), 'addToCart']] }]
ومع ذلك ، يجب تغيير تنسيق البيانات في دفق setDetails قليلاً:
<input name="name" onChange={ $('setDetails', e => [{ name: e.target.value }, props.i]) } /> <input name="surname" onChange={ $('setDetails', e => [{ surname: e.target.value }, props.i]) }/>
باستخدام عامل
Coll ، نقوم بوصف ثلاثة خيوط ستؤثر على الصفيف الخاص بنا. في هذه الحالة ، يجب أن يحتوي دفق
الإنشاء على العناصر نفسها ، والتي يجب إضافتها إلى الصفيف (عادةً كائنات). يقبل دفق
الحذف إما مؤشرات العناصر المراد حذفها (سواء في '* / removeSeat') والأقنعة. سيحذف القناع {} جميع العناصر ، على سبيل المثال ، القناع {name: 'Carl'} سيحذف جميع العناصر التي تحمل الاسم Carl. يقبل دفق
التحديث أزواج من القيم: التغيير الذي يجب القيام به مع العنصر (القناع أو الوظيفة) ، وفهرس أو قناع العناصر التي تحتاج إلى تغيير. على سبيل المثال ، [{اللقب: 'Johnson'} ، {}] سيضبط اسم جونسون على جميع عناصر المصفوفة.
يستخدم عامل Coll شيئًا مثل لغة استعلام داخلية ، مما يسهل العمل مع المجموعات ويجعلها أكثر تعبيريًا.
الكود الكامل لتطبيقنا في JsFiddle.
تعرفنا على جميع الوظائف الأساسية اللازمة للسيد. من الموضوعات المهمة إلى حد ما التي بقيت في البحر إدارة الثروات العالمية ، والتي يمكن مناقشتها في المقالات المستقبلية. ولكن الآن يمكنك البدء في استخدام mrr لإدارة الحالة داخل مكون أو مجموعة من المكونات ذات الصلة.
الاستنتاجات
ما هي قوة السيد؟
يسمح لك mrr بكتابة التطبيقات في React بأسلوب تفاعلي وظيفي (يمكن فك تشفير mrr على أنه Make React Reactive). mrr معبّر جدًا - تقضي وقتًا أقل في كتابة أسطر التعليمات البرمجية.
يوفر mrr مجموعة صغيرة من التجريد الأساسي ، وهو ما يكفي لجميع الحالات - تصف هذه المقالة تقريبًا جميع الميزات والتقنيات الرئيسية للسيد.
هناك أيضًا أدوات لتوسيع هذه المجموعة الأساسية (القدرة على إنشاء عوامل تشغيل مخصصة). يمكنك كتابة شفرة تعريفية جميلة دون قراءة مئات الصفحات من الدليل وبدون حتى دراسة الأعماق النظرية للبرمجة الوظيفية - من غير المرجح أن تستخدم ، على سبيل المثال ، monads ، لأنه mrr نفسه هو monad عملاق يفصل الحساب النقي عن طفرات الدولة.بينما في المكتبات الأخرى ، تتعايش المناهج غير المتجانسة (الحتمية باستخدام الطرق والإعلانية باستخدام المجلدات التفاعلية) ، والتي يمزج المبرمج منها "السلطة" بشكل عشوائي ، في mrr هناك جوهر أساسي واحد - تيار ، مما يساهم في تجانس التعليمات البرمجية والتوحيد. الراحة ، الراحة ، البساطة ، توفير وقت المبرمج هي المزايا الرئيسية للسيد (من هنا هو فك تشفير آخر للسيد باسم "mrrrr" ، أي خرقة قطة راضية عن حياة القطة).ما هي السلبيات؟
البرمجة ب "الخطوط" لها مزاياها وعيوبها. لن تتمكن من الإكمال التلقائي لاسم الخلية ، أو البحث عن المكان الذي تم تعريفه فيه. من ناحية أخرى ، في mrr يوجد دائمًا مكان واحد فقط حيث يتم تحديد سلوك الخلية ، ومن السهل العثور عليه باستخدام بحث نص بسيط ، أثناء البحث عن المكان الذي يتم تحديد قيمة حقل Redux في المتجر ، أو حتى أقل من حقل الحالة عند استخدام setState الأصلي قد تكون أطول.من قد يكون مهتمًا بهذا؟
بادئ ذي بدء ، أتباع البرمجة الوظيفية هم الأشخاص الذين تتضح لهم ميزة النهج الإعلاني. بالطبع ، توجد حلول ClojureScript kosher بالفعل ، لكنها لا تزال منتجًا متخصصًا ، في حين أن React يحكم الكرة. إذا كان مشروعك يستخدم Redux بالفعل ، فيمكنك البدء في استخدام mrr لإدارة الحالة المحلية ، وفي المستقبل التحول إلى عالمي. حتى إذا كنت لا تخطط لاستخدام تقنيات جديدة في الوقت الحالي ، يمكنك التعامل مع mrr من أجل "تمديد عقلك" من خلال النظر في المهام المألوفة في ضوء جديد ، لأن mrr يختلف اختلافًا كبيرًا عن مكتبات إدارة الحالة العامة.هل يمكن استخدام هذا بالفعل؟
من حيث المبدأ ، نعم :) المكتبة شابة ، حتى الآن تم استخدامها بنشاط في العديد من المشاريع ، ولكن تم بالفعل إنشاء واجهة برمجة التطبيقات للوظائف الأساسية ، والآن يتم العمل بشكل أساسي على المستحضرات المختلفة (السكر النحوي) ، المصممة لزيادة تسريع وتيسير التنمية. بالمناسبة ، في مبادئ السيد لا يوجد شيء محدد لرد فعل ، يمكن تكييفه للاستخدام مع أي مكتبة مكون (تم اختيار رد الفعل بسبب عدم وجود تفاعل مدمج أو مكتبة مقبولة بشكل عام لهذا).شكرا لكم على اهتمامكم ، وسأكون ممتنا للتعليقات والنقد البناء!