تكافح من أجل أداء أشكال تفاعل كبيرة حقًا

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



صورة


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



لذلك ، في مشروعنا ، نواجه شكلًا من العديد من الكتل بخصائص معينة:



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

  • – , , ( 10 , ) .
  • , , . , 10- , . : .
  • . . .


احتل الشكل النهائي حوالي 6 آلاف بكسل عموديًا - أي حوالي 3-4 شاشات ، في المجموع ، أكثر من 80 حقلاً مختلفًا. بالمقارنة مع هذا النموذج ، فإن طلب خدمات الدولة لا يبدو رائعًا. ربما يكون أقرب شيء من حيث وفرة الأسئلة هو استبيان من خدمة الأمن إلى شركة كبيرة أو استطلاع رأي ممل حول تفضيلات محتوى الفيديو.



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



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



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



كيف نشرنا Final-Form



استخدم المشروع React و TypeScript (عندما أكملنا مهامنا ، تحولنا تمامًا إلى TypeScript). لذلك ، لتنفيذ النماذج ، أخذنا مكتبة React Final-form من مبتكري نموذج Redux.



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



التبعيات



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



لقد قمنا بعدة محاولات لتحسين الوضع.



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



<Form onSubmit={this.handleSubmit} initialValues={initialValues}>
   {({values, error, ...other}) => (
      <>
      <Block1 data={selectDataForBlock1(values)}/>
      <Block2 data={selectDataForBlock2(values)}/>
      ...
      <BlockN data={selectDataForBlockN(values)}/>
      </>
   )}
</Form>


كما يمكنك أن تتخيل ، كان علي أن أخرج بنفسي memoize pick([field1, field2,...fieldn]).



أدى كل هذا جنبًا إلى جنب مع PureComponent (React.memo, reselect)حقيقة أن الكتل يتم إعادة رسمها فقط عندما تتغير البيانات التي تعتمد عليها (نعم ، لقد أدخلنا مكتبة Reselect في المشروع ، والتي لم يتم استخدامها سابقًا ، وبمساعدتها نقوم بتنفيذ جميع طلبات البيانات تقريبًا).



نتيجة لذلك ، قمنا بالتبديل إلى مستمع واحد يصف كل التبعيات الخاصة بالنموذج. لقد أخذنا فكرة هذا النهج من مشروع الحساب النهائي ( https://github.com/final-form/final-form-calculate ) ، مضيفًا إياه إلى احتياجاتنا.



<Form
   onSubmit={this.handleSubmit}
   initialValues={initialValues}
   decorators={[withContextListenerDecorator]}
>

   export const listenerDecorator = (context: IContext) =>
   createDecorator(
      ...block1FieldListeners(context),
      ...block2FieldListeners(context),
      ...
   );

   export const block1FieldListeners = (context: any): IListener[] => [
      {
      field: 'block1Field',
      updates: (value: string, name: string) => {
         //    block1Field       ...
         return {
            block2Field1: block2Field1NewValue,
            block2Field2: block2Field2NewValue,
         };
      },
   },
];


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



التحقق من الصحة



عن طريق القياس مع التبعيات ، تعاملنا مع التحقق من الصحة.



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



  • المدقق لبيانات جواز السفر ،
  • المدقق لبيانات الرحلة ،
  • للحصول على بيانات عن التأشيرات الصادرة سابقًا ،
  • إلخ


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



إعادة استخدام الكود



بدأنا بنموذج واحد كبير ، طرحنا عليه أفكارنا ، ولكن مع مرور الوقت ، نما المشروع - وظهر شكل آخر. بطبيعة الحال ، في النموذج الثاني ، استخدمنا كل الأفكار نفسها ، بل وأعدنا استخدام الكود.



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



وبالمثل ، يحتوي النموذج الجديد الآن على أنواع وثوابت ومكونات مشتركة مع النموذج القديم - على سبيل المثال ، لديهم تفويض عام.



بدلا من المجاميع



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



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



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



مؤلفو المقال: Oleg Troshagin، Maxilekt.



ملاحظة: ننشر مقالاتنا على عدة مواقع على Runet. الاشتراك في صفحاتنا في VK ، FB ، إينستاجرام أو قناة برقية للتعرف على جميع منشوراتنا والأخبار الأخرى من Maxilect.



All Articles