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

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

هناك وحدتان. بتعبير أدق ، وحدة واحدة مباشرة هي قصيدة. وجذر التطبيق مع الوظائف المشتركة للنظام بأكمله هو التطبيق. يوجد لدينا معلومات عن المستخدم ، عرض الرسائل للمستخدم. كل وحدة لها مخفضاتها ، إجراءاتها ، ضوابطها ، إلخ. مع نمو التطبيق ، تتكاثر الوحدات الجديدة.
تشكل سلسلة من المخفضات ، باستخدام redux-immutable ، الحالة الثابتة بالكامل التالية:

كيف يعمل:
1. التحكم في إرسال العمل المنشئ:
import at from '../constants/actionTypes'; export function poemTextChange(text) { return function (dispatch, getstate) { dispatch({ type: at.POEM_TYPE, payload: text }); }; }
يتم نقل ثوابت أنواع الإجراءات إلى ملف منفصل. أولاً ، نحن في مأمن من الأخطاء المطبعية. ثانياً ، سيكون التحسس متوفراً لنا.
2. ثم يتعلق الأمر بالمخفض.
import logic from '../logic/poem'; export default function poemScoringReducer(state = Immutable.Map(), action) { switch (action.type) { case at.POEM_TYPE: return logic.onType(state, action.payload); default: return state; } }
يتم نقل معالجة المنطق إلى
وظيفة حالة منفصلة. خلاف ذلك ، سوف يصبح رمز المخفض غير قابل للقراءة بسرعة.
3. منطق معالجة النقرات باستخدام التحليل المعجمي والذكاء الاصطناعي:
export default { onType(state, text) { return state .set('poemText', text) .set('score', this.calcScore(text)); }, calcScore(text) { const score = Math.floor(text.length / 10); return score > 5 ? 5 : score; } };
في حالة زر "قصيدة جديدة" ، لدينا منشئ الفعل التالي:
export function newPoem() { return function (dispatch, getstate) { dispatch({ type: at.POEM_TYPE, payload: '' }); dispatch({ type: appAt.SHOW_MESSAGE, payload: 'You can begin a new poem now!' }); }; }
أولاً ، أرسل نفس الإجراء الذي يعيد تعيين نصنا ونقاطنا. بعد ذلك ، أرسل الإجراء ، الذي سيتم التقاطه بواسطة مخفض آخر وعرض رسالة للمستخدم.
كل شيء جميل. دعونا نخلق مشاكل لأنفسنا:
المشاكل:
لقد نشرنا طلبنا. لكن مستخدمينا ، بالنظر إلى أنهم طُلب منهم كتابة الشعر ، بدأوا بشكل طبيعي في نشر أعمالهم ، وهو ما لا يتوافق مع معايير الشركة للغة الشعرية. وبعبارة أخرى ، نحن بحاجة إلى تعديل الكلمات الفاحشة.
ماذا سنفعل:
- في نص الإدخال ، من الضروري استبدال جميع الكلمات غير المستنبتة بـ * رقابة *
- بالإضافة إلى ذلك ، إذا كان المستخدم قد ألقى بكلمة قذرة ، فيجب تحذيره برسالة تفيد بأنه يرتكب خطأ.
جيد. نحتاج فقط إلى تحليل النص ، بالإضافة إلى حساب النتيجة ، لاستبدال الكلمات السيئة. لا مشكلة. وأيضًا ، لإعلام المستخدم ، تحتاج إلى قائمة بما قمنا بحذفه. كود المصدر
هنا .
نعيد تشكيل وظيفة المنطق بحيث تقوم ، بالإضافة إلى الحالة الجديدة ، بإرجاع المعلومات اللازمة للرسالة إلى المستخدم (الكلمات المستبدلة):
export default { onType(state, text) { const { reductedText, censoredWords } = this.redactText(text); const newState = state .set('poemText', reductedText) .set('score', this.calcScore(reductedText)); return { newState, censoredWords }; }, calcScore(text) { const score = Math.floor(text.length / 10); return score > 5 ? 5 : score; }, redactText(text) { const result = { reductedText:text }; const censoredWords = []; obscenseWords.forEach((badWord) => { if (result.reductedText.indexOf(badWord) >= 0) { result.reductedText = result.reductedText.replace(badWord, '*censored*'); censoredWords.push(badWord); } }); if (censoredWords.length > 0) { result.censoredWords = censoredWords.join(' ,'); } return result; } };
دعنا نطبقها الآن. لكن كيف؟ في المخفض ، ليس من المنطقي بالنسبة لنا أن نطلق عليه بعد الآن ، لأننا سنضع النص والتقييم في الدولة ، ولكن ماذا نفعل مع الرسالة؟ من أجل إرسال رسالة ، على أي حال ، سيتعين علينا إرسال الإجراء المقابل. لذا ، نحن ننهي العمل المبدع.
export function poemTextChange(text) { return function (dispatch, getState) { const globalState = getState(); const scoringStateOld = globalState.get('poemScoring');
مطلوب أيضًا تعديل المخفض ، لأنه لم يعد يستدعي وظيفة المنطق:
switch (action.type) { case at.POEM_TYPE: return action.payload; default: return state;
ماذا حدث:
والآن ، السؤال هو. لماذا نحتاج إلى مخفض ، في معظم الأحيان ، سيعيد ببساطة الحمولة بدلاً من حالة جديدة؟ عندما تظهر إجراءات أخرى تعالج منطق الإجراء ، هل سيكون من الضروري تسجيل نوع إجراء جديد؟ أو ربما إنشاء SET_STATE واحد مشترك؟ ربما لا ، لأن المفتش سيكون حينها فوضى. لذا سننتج نفس النوع من الحالات؟
جوهر المشكلة على النحو التالي. إذا كانت معالجة المنطق تنطوي على العمل مع قطعة من الدولة ، والتي يتحملها العديد من المخفضات ، فعليك كتابة جميع أنواع الانحرافات. على سبيل المثال ، النتائج الوسيطة لوظائف الحالة ، والتي يجب أن تكون مبعثرة عبر مخفضات مختلفة باستخدام إجراءات متعددة.
موقف مماثل ، إذا كانت وظيفة الحالة تحتاج إلى معلومات أكثر مما هو موجود في المخفض الخاص بك ، فعليك إجراء نداءها إلى الإجراء ، حيث يوجد الوصول إلى الحالة العالمية ، متبوعًا بإرسال الحالة الجديدة كحمولة. يجب تقسيم المخفضات في أي حال ، إذا كان هناك الكثير من المنطق في الوحدة. وهذا يخلق إزعاجًا كبيرًا.
دعونا نلقي نظرة على الوضع من جانب واحد. في عملنا ، نحصل على قطعة من الدولة من العالمية. هذا ضروري
لتحويله (globalState.get ('poemScoring')؛ ). اتضح أننا نعرف بالفعل في الواقع ما هي الحالة التي يجري العمل بها. لدينا دولة جديدة. نحن نعرف أين نضعها. ولكن بدلاً من وضعه في حالة عالمية ، نقوم بتشغيله بنوع من النص الثابت طوال سلسلة المخفضات بحيث يمر عبر كل علبة تبديل ويستبدلها مرة واحدة. لي من إدراك هذا ، التجاعيد. أفهم أن ذلك يتم من أجل سهولة التطوير وتقليل الاتصال. ولكن في حالتنا ، لم يعد لها دور.
الآن ، سأدرج جميع النقاط التي لا أحبها في التنفيذ الحالي ، إذا كان يجب تحجيمها في اتساع وعمق لفترة غير محدودة :
- إزعاج كبير عند العمل مع دولة خارج المخفض.
- مشكلة فصل الكود. في كل مرة نرسل فيها إجراء ، يمر عبر كل مخفض ، يمر عبر كل حالة. من السهل عدم الإزعاج عندما يكون لديك تطبيق صغير. ولكن ، إذا كان لديك وحش تم بناؤه لعدة سنوات مع عشرات المخفضات ومئات الحالات ، فعندئذ أفكر في جدوى مثل هذا النهج. ربما ، حتى مع آلاف الحالات ، لن يكون لذلك تأثير كبير على الأداء. ولكن ، بفهم أنه عند طباعة النص ، ستتسبب كل صحافة في المرور عبر مئات الحالات ، لا يمكنني تركها كما هي. أي ، أصغر تأخر ، مضروبًا في اللانهاية ، يميل إلى اللانهاية. وبعبارة أخرى ، إذا كنت لا تفكر في مثل هذه الأشياء ، عاجلاً أم آجلاً ، فسوف تنشأ مشاكل.
ما هي الخيارات؟
أ. التطبيقات المعزولة مع مزوديها . في كل وحدة (تطبيق فرعي) ، سيكون عليك تكرار الأجزاء العامة للدولة (الحساب ، الرسائل ، إلخ).
ب. استخدم مخفضات غير متزامنة قابلة للتوصيل. لا ينصح بذلك دان نفسه.
ج. استخدم مرشحات العمل في المخفضات. بمعنى ، يجب أن يكون كل إرسال مصحوبًا بمعلومات حول الوحدة النمطية التي يتم إرسالها إليها. وفي مخفضات الوحدات ، اكتب الشروط المناسبة. لقد حاولت. لم يكن هناك مثل هذا العدد من الأخطاء اللاإرادية سواء قبل أو بعد. هناك ارتباك مستمر حول أين يذهب العمل. - في كل مرة يتم فيها إرسال إجراء ، لا يتم تشغيل كل مخفض فقط ، ولكن أيضًا مجموعة الحالة العكسية. لا يهم إذا كانت الحالة قد تغيرت في المخفض - سيتم استبدالها في مجتمعة.
- يفرض كل إرسال على MapStateToProps أن تتم معالجته لكل مكون مرفق مركب على الصفحة. إذا قمنا بتقسيم المخفضات ، فعلينا تقسيم المراتب. هل من الضروري أن يكون لدينا زر يستبدل النص ويعرض الرسالة بإرساليات مختلفة؟ ربما لا. ولكن لدي تجربة تحسين ، عندما يسمح تقليل عدد الإرساليات من 15 إلى 3 بزيادة استجابة النظام بشكل كبير ، بنفس المقدار من منطق الأعمال المجهزة. أعلم أن هناك مكتبات يمكنها الجمع بين عدة إرساليات في دفعة واحدة ، ولكن هذا صراع مع التحقيق باستخدام العكازات.
- عند سحق الإرساليات ، يكون من الصعب في بعض الأحيان رؤية ما يحدث. لا يوجد مكان واحد ، كل شيء مشتت على ملفات مختلفة. من الضروري البحث حيث يتم تنفيذ المعالجة عن طريق البحث عن الثوابت في جميع رموز المصدر.
- في التعليمات البرمجية أعلاه ، تصل المكونات والإجراءات إلى الحالة العالمية مباشرة:
const userName = globalState.getIn(['app', 'account', 'name']); … const text = state.getIn(['poemScoring', 'poemText']);
هذا ليس جيدًا لعدة أسباب:
أ. يجب عزل الوحدات بشكل مثالي. إنهم لا يحتاجون إلى معرفة المكان الذي يعيشون فيه.
ب. غالبًا ما يكون ذكر المسارات نفسها في أماكن مختلفة محفوفًا ليس بالأخطاء / الأخطاء المطبعية فحسب ، بل يجعل إعادة الهيكلة أمرًا صعبًا للغاية في حالة تغيير تكوين الحالة العالمية ، أو تغيير طريقة تخزينها.
- بشكل متزايد ، أثناء كتابة إجراء جديد ، كان لدي انطباع بأنني كنت أكتب التعليمات البرمجية من أجل التعليمات البرمجية. افترض أننا نريد إضافة مربع اختيار إلى الصفحة وتعكس حالتها المنطقية في القصة. إذا أردنا تنظيمًا موحدًا للعمل / المخفضات ، فعلينا:
- تسجيل ثابت من نوع العمل
- اكتب حفرة عمل
- في عنصر التحكم ، قم باستيراده وتسجيله في mapDispatchToProps
- التسجيل في PropTypes
- قم بإنشاء handleCheckBoxClick في عنصر التحكم وحدده في خانة الاختيار
- إضافة مفتاح في المخفض باستدعاء وظيفة الحالة
- كتابة دالة حالة في المنطق
من أجل شيك ملاكمة واحد! - الحالة التي يتم إنشاؤها باستخدام CombUrucers ثابتة. لا يهم إذا أدخلت الوحدة B بالفعل أم لا ، فستكون هذه القطعة في القصة. فارغة ، لكنها ستكون. من غير الملائم استخدام المفتش عندما يكون هناك الكثير من العقد الفارغة غير المستخدمة في القدم.
كيف نحاول حل بعض المشاكل الموضحة أعلاه
لذا ، حصلنا على مخفضات غبية ، وفي فوهات الحركة / المنطق ، نكتب أعمدةًا من التعليمات البرمجية للعمل مع الهياكل الثابتة غير الثابتة. للتخلص من هذا ، أستخدم آلية المحددات الهرمية ، التي لا تسمح فقط بالوصول إلى الدولة المرغوبة ، ولكن أيضًا استبدالها (setIn مريحة). لقد نشرت هذا في حزمة
المحددات الثابتة .
دعونا نلقي نظرة على مثالنا كيف يعمل (
المستودع ):
في وحدة poemScoring ، نصف كائن المحددات. نحن نصف تلك الحقول من الدولة التي نريد أن نمتلك حق الوصول المباشر للقراءة / الكتابة. يسمح بأي تداخل ومعلمات للوصول إلى عناصر المجموعات. ليس من الضروري وصف جميع الحقول الممكنة في مقالتنا.
import extendSelectors from 'immutable-selectors'; const selectors = { poemText:{}, score:{} }; extendSelectors(selectors, [ 'poemScoring' ]); export default selectors;
علاوة على ذلك ، تحول طريقة extensionSelectors كل حقل في كائننا إلى وظيفة محدد. تشير المعلمة الثانية إلى المسار إلى جزء الحالة الذي يتحكم فيه المحدد. نحن لا ننشئ كائنًا جديدًا ، ولكننا نغير الكائن الحالي. هذا يعطينا مكافأة في شكل ذكاء عامل:

ما هو هدفنا - محدد بعد توسعه:

تقوم دالة
selectors.poemText (state) ببساطة بتنفيذ
state.getIn (['poemScoring'، 'poemText']) .
جذر الوظيفة
(الحالة) - يحصل على "poemScoring".
لكل محدد وظيفة
الاستبدال الخاصة به
(globalState ، newPart) ، والتي من خلال setIn تُرجع حالة عمومية جديدة مع استبدال الجزء المقابل.
أيضا ، يتم إضافة كائن
مسطح يتم تكرار جميع مفاتيح المحدد الفريدة. أي إذا استخدمنا حالة عميقة من النموذج
selectors = { dive:{ in:{ to:{ the:{ deep:{} } } } }}
يمكنك التعمق عميقًا في
selectors.dive.in.to.the.deep (state) أو as
selectors.flat.deep (state) .
المضي قدما. نحتاج إلى تحديث الحصول على البيانات في عناصر التحكم:
قصيدة:
function mapStateToProps(state, ownprops) { return { text:selectors.poemText(state) || '' }; }
النتيجة:
function mapStateToProps(state, ownprops) { const score = selectors.score(state); return { score }; }
بعد ذلك ، قم بتغيير مخفض الجذر:
import initialState from './initialState'; function setStateReducer(state = initialState, action) { if (action.setState) { return action.setState; } else { return state;
إذا رغبت في ذلك ، يمكننا الجمع بين استخدام المخفضات.
فوهة العمل ، على سبيل المثال ، poemTextChange:
export function poemTextChange(text) { return function (dispatch, getState) { dispatch({ type: 'Poem typing', setState: logic.onType(getState(), text), payload: text }); }; }
لم يعد بإمكاننا استخدام ثوابت من نوع الإجراء ، لأن النوع يستخدم الآن فقط للتصور في المفتش. نحن في المشروع نكتب أوصاف النص الكامل للعمل باللغة الروسية. يمكنك أيضًا التخلص من الحمولة ، لكنني أحاول حفظها حتى أفهم في المفتش ، إذا لزم الأمر ، ما هي المعلمات التي تم استدعاء الإجراء.
وفي الواقع ، المنطق نفسه:
onType(gState, text) { const { reductedText, censoredWords } = this.redactText(text); const poemState = selectors.root(gState) || Immutable.Map();
في الوقت نفسه ،
يتم استيراد
message.showMessage من منطق الوحدة النمطية المجاورة ، والتي تصف محدداتها:
showMessage(gState, text) { return selectors.message.text.replace(gState, text); }.
ما اتضح:

لاحظ أنه كان لدينا إرسال واحد ، تغيرت البيانات في وحدتين.
كل هذا سمح لنا بالتخلص من المخفضات والثوابت من نوع العمل ، وكذلك حل أو التغلب على معظم الاختناقات الموضحة أعلاه.
وإلا كيف يمكن تطبيق ذلك؟
هذا النهج مناسب للاستخدام عندما يكون من الضروري التأكد من أن عناصر التحكم أو الوحدات النمطية الخاصة بك توفر العمل مع أجزاء مختلفة من الحالة. دعنا نقول أن قصيدة واحدة ليست كافية بالنسبة لنا. نريد أن يكون المستخدم قادرًا على تأليف القصائد على علامتي تبويب مختلفتين في تخصصات مختلفة (أطفال ، رومانسي). في هذه الحالة ، لا يمكننا استيراد المحددات في المنطق / عناصر التحكم ، ولكن تحديدها كمعلمة في عنصر التحكم الخارجي:
<Poem selectors = {selectors.hildPoem}/> <Poem selectors = {selectors.romanticPoem}/>
علاوة على ذلك ، قم بتمرير هذه المعلمة إلى فوهات العمل. هذا يكفي لجعل تركيبة معقدة من المكونات والمنطق مغلقة بالكامل ، مما يسهل إعادة استخدامها.
القيود عند استخدام المحددات الثابتة:لن يعمل على استخدام المفتاح في حالة "الاسم" ، لأنه بالنسبة للوظيفة الأصلية ستكون هناك محاولة لتجاوز الخاصية المحجوزة.
ما هي النتيجة
ونتيجة لذلك ، تم الحصول على نهج مرن إلى حد ما ، وتم القضاء على علاقات الشفرة الضمنية من خلال ثوابت النص ، وتم تقليل النفقات العامة مع الحفاظ على راحة التطوير. هناك أيضًا مفتش redux يعمل بشكل كامل مع إمكانية السفر عبر الزمن. ليس لدي رغبة في العودة إلى مخفضات قياسية.
بشكل عام ، هذا كل شيء. شكرا لك على وقتك. ربما شخص ما سيهتم بتجربته!