السعر الخفي لمكتبات CSS-in-JS في تطبيقات React

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



نظرة عامة على الوضع


في شركتي ، تقرر إنشاء مكتبة واجهة المستخدم. هذا من شأنه أن يجلب لنا فائدة كبيرة ، سيتيح لنا إعادة استخدام شظايا القياسية من واجهات في مشاريع مختلفة. كنت أحد المتطوعين الذين تولوا المهمة. قررت استخدام تقنية CSS-in-JS ، لأنني كنت على دراية بواجهة برمجة تطبيقات التصميم لمعظم مكتبات CSS-in-JS الشائعة. في مجرى العمل ، حاولت العمل بشكل معقول. لقد صممت منطق قابلة لإعادة الاستخدام وتطبيق الخصائص المشتركة في المكونات. لذلك ، أخذت تكوين المكونات. على سبيل المثال ، قام <IconButton /> بتوسيع المكون <BaseButton /> ، والذي كان بدوره styled.button لكيان styled.button بسيط. لسوء الحظ ، اتضح أن IconButton يحتاج إلى تصميم خاص به ، لذلك قمت بتحويل هذا المكون إلى مكون منمق:

 const IconButton = styled(BaseButton)`  border-radius: 3px; `; 

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

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

وغني عن القول ، كانت الشجرة التي ولدتها React ضخمة حقا. كان عدد مكونات Context.Consumer كبيرًا لدرجة أنه قد يحرمني من النوم. والحقيقة هي أنه في كل مرة يتم تقديم مكون ذي نمط مفرد باستخدام مكونات نمطية أو عاطفة ، بالإضافة إلى إنشاء مكون React منتظم ، يتم أيضًا Context.Consumer مكون إضافي Context.Consumer . يعد ذلك ضروريًا للسماح للبرنامج النصي المقابل (تعتمد معظم مكتبات CSS-in-JS على البرامج النصية التي يتم تنفيذها أثناء تشغيل الصفحة) لمعالجة قواعد التصميم التي تم إنشاؤها. عادةً لا يتسبب هذا في أي مشاكل خاصة ، لكن يجب ألا ننسى أن المكونات يجب أن يكون لها وصول إلى الموضوع. هذا يترجم إلى الحاجة إلى تقديم Context.Consumer إضافية لكل عنصر من العناصر ، مما يتيح لك "قراءة" السمة من مكون ThemeProvider . نتيجة لذلك ، عند إنشاء مكون منمق في تطبيق ذو سمة ، يتم إنشاء 3 مكونات. هذا مكون StyledXXX واحد ومكونات Context.Consumer .

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

جانبي


من أجل اختبار حلول CSS-in-JS المختلفة ، قمتُ بإنشاء تطبيق بسيط. يعرض النص Hello World 50 مرة. في الإصدار الأول من هذا التطبيق ، قمت بلف هذا النص في عنصر div منتظم. في الثانية ، استخدمت مكون styled.div . بالإضافة إلى ذلك ، أضفت زرًا للتطبيق يؤدي إلى إعادة عرض كل هذه العناصر الخمسين.

بعد تقديم المكون <App /> ، تم عرض شجرتي React مختلفتين. توضح الأشكال التالية أشجار العناصر المستخلصة من React.


شجرة معروضة في تطبيق يستخدم عنصر div منتظم


شجرة معروضة في تطبيق يستخدم styled.div

بعد ذلك ، باستخدام الزر ، قمت بعرض <App /> 10 مرات من أجل جمع البيانات المتعلقة بالتحميل على النظام ، والتي Context.Consumer مكونات إضافية في Context.Consumer . فيما يلي معلومات حول إعادة تقديم تطبيق بشكل متكرر مع عناصر div منتظمة في وضع التصميم.


إعادة تقديم التطبيق مع عناصر div العادية في وضع التصميم. القيمة المتوسطة هي 2.54 مللي ثانية.


إعادة تقديم التطبيق مع عناصر styled.div في وضع التطوير. القيمة المتوسطة هي 3.98 مللي ثانية.

ما يثير الاهتمام هو أن تطبيق CSS-in-JS ، في المتوسط ​​، أبطأ بنسبة 56.6٪ من المعتاد. لكنه كان وضع التنمية. ماذا عن وضع الإنتاج؟


إعادة تقديم التطبيق مع عناصر div المعتادة في وضع الإنتاج. القيمة المتوسطة هي 1.06 مللي ثانية.


إعادة تقديم التطبيق مع عناصر styled.div في وضع الإنتاج. القيمة المتوسطة هي 2.27 مللي ثانية.

عند تشغيل وضع الإنتاج ، يبدو تنفيذ div للتطبيق أسرع بنسبة تزيد عن 50٪ مقارنة بنفس الإصدار في وضع التطوير. تطبيق styled.div أسرع بنسبة 43٪ فقط. وهنا ، كما كان من قبل ، من الواضح أن حل CSS-in-JS يكون بطيئًا تقريبًا ضعف الحل المعتاد. ما الذي يبطئه؟

تحليل التطبيق أثناء تنفيذه


يمكن أن تكون الإجابة الواضحة على سؤال ما الذي يبطئ تطبيق CSS-in-JS كما يلي: "قيل إن مائة مكتبة CSS-in-JS تقدم اثنين من Context.Consumer لكل مكون". ولكن إذا فكرت في كل هذا Context.Consumer ، فإن Context.Consumer هو مجرد آلية للوصول إلى متغير JS. بالطبع ، يحتاج React إلى القيام ببعض الأعمال من أجل تحديد مكان قراءة القيمة المقابلة ، ولكن هذا وحده لا يفسر نتائج القياس المذكورة أعلاه. يمكن العثور على الإجابة الحقيقية على هذا السؤال من خلال تحليل سبب استخدام Context.Consumer . الحقيقة هي أن معظم مكتبات CSS-in-JS تعتمد على البرامج النصية التي تعمل أثناء إخراج الصفحة في المستعرض ، والتي تساعد المكتبات على تحديث أنماط المكونات ديناميكيًا. لا تنشئ هذه المكتبات فئات CSS أثناء تجميع الصفحة. بدلاً من ذلك ، يقومون بإنشاء وتحديث علامات <style> في المستند ديناميكيًا. يتم ذلك عند تركيب المكونات ، أو عندما تتغير خصائصها. تحتوي هذه العلامات عادةً على فئة CSS مفردة يعين اسمها المجزأة لمكون React واحد. عند تغيير خصائص هذا المكون ، يجب أيضًا تغيير العلامة <style> المقابلة. إليك كيفية وصف ما يجري خلال هذه العملية:

  • يتم إعادة إنشاء قواعد CSS التي يجب أن تحتوي عليها العلامة <style> .
  • يتم إنشاء اسم فئة تجزئة جديد يُستخدم لتخزين قواعد CSS المذكورة أعلاه.
  • يتم تحديث خاصية classname الخاصة بمكون React المقابل إلى واحدة جديدة ، مما يشير إلى الفئة التي تم إنشاؤها للتو.

النظر ، على سبيل المثال ، مكتبة styled-components . عند إنشاء مكون styled.div المكتبة بتعيين معرف داخلي (ID) لهذا المكون وإضافة علامة <style> جديدة إلى علامة HTML <head> . تحتوي هذه العلامة على تعليق واحد يشير إلى المعرف الداخلي لمكون React الذي ينتمي إليه النمط المقابل:

 <style data-styled-components>   /* sc-component-id: sc-bdVaJa */ </style> 

وإليك الإجراءات التي تنفذها مكتبة المكونات المصممة عند عرض المكون التفاعلي المقابل:

  1. يوزع قواعد CSS من سلسلة قالب styled-components.
  2. يولد اسم فئة CSS جديدًا (أو يكتشف ما إذا كان سيتم الاحتفاظ بالاسم السابق).
  3. ينفذ المعالجة المسبقة للأنماط باستخدام الأسلوب.
  4. يدمج CSS الناتج عن المعالجة المسبقة في العلامة <style> المقابلة لعلامة HTML <head> .

لتتمكن من استخدام الموضوع في الخطوة رقم 1 من هذه العملية ، يلزم Context.Consumer . بفضل هذا المكون ، تتم قراءة القيم من الموضوع في سلسلة القالب. لتتمكن من تعديل العلامة <style> المرتبطة بهذا المكون ، هناك حاجة إلى Context.Consumer واحد آخر من المكون. يسمح لك بالوصول إلى مثيل ورقة أنماط. لهذا السبب نجد في معظم مكتبات CSS-in-JS حالتين من Context.Consumer .

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

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

 const StaticStyledDiv = styled.div`  color:red `; 

وهذا المكون ليس ثابتًا:

 const DynamicStyledDiv = styled.div`  color: ${props => props.color} `; 

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

إذا كنت حريصًا بشكل كافٍ عند تحليل لقطات الشاشة المقدمة مسبقًا ، فقد تلاحظ أنه حتى في وضع الإنتاج لكل عنصر من عناصر styled.div . ومن المثير للاهتمام أن المكون الذي تم تقديمه كان "ثابتًا" ، نظرًا لعدم وجود قواعد CSS ديناميكية مرتبطة به. في مثل هذه الحالة ، يتوقع المرء أنه إذا تمت كتابة هذا المثال باستخدام مكتبة المكونات المصممة ، فلن Context.Consumer اللازمة Context.Consumer مع السمة. السبب وراء عرض اثنين من Context.Consumer بالضبط هنا هو أن التجربة ، التي تم تقديم بياناتها أعلاه ، تم تنفيذها باستخدام العاطفة - مكتبة CSS-in-JS أخرى. تأخذ هذه المكتبة نفس النهج الذي تتبعه مكونات التصميم. الاختلافات بينهما صغيرة. لذلك ، تقوم مكتبة العاطفة بتوزيع سلسلة القالب ، وتقوم بمعالجة الأنماط باستخدام الأسلوب ، وتحديث محتويات <style> المقابلة. هنا ، ومع ذلك ، ينبغي ملاحظة اختلاف رئيسي واحد بين المكونات على غرار والعاطفة. يتكون ذلك في حقيقة أن مكتبة العاطفة تلتف دائمًا بجميع المكونات في ThemeContext.Consumer - بغض النظر عما إذا كانت تستخدم السمة أم لا (يشرح هذا مظهر لقطة الشاشة أعلاه). ومن المثير للاهتمام ، على الرغم من أن العاطفة تجعل مكونات المستهلك أكثر من المكونات المصممة ، فإن العواطف تتفوق على المكونات المصممة من حيث الأداء. يشير هذا إلى أن عدد مكونات Context.Consumer ليس عاملاً رئيسياً في إبطاء التقديم. تجدر الإشارة إلى أنه في وقت كتابة هذه المادة ، تم إصدار نسخة تجريبية من المكونات ذات النمط v5.xx ، والتي ، طبقًا لمطوري المكتبة ، تتجاوز المشاعر من حيث الأداء.

تلخيص ما كنا نتحدث عنه. اتضح أن مجموعة من العديد من عناصر Context.Consumer (مما يعني أن React يجب أن تنسق عمل عناصر إضافية) وآليات التصميم الديناميكية الداخلية يمكن أن تبطئ التطبيق. يجب أن أقول أنه لا يتم حذف جميع العلامات <style> تمت إضافتها إلى <head> لكل مكون. هذا يرجع إلى حقيقة أن عمليات إزالة العنصر تنشئ حمولة كبيرة على DOM (على سبيل المثال ، يجب على المتصفح إعادة ترتيب الصفحة بسبب هذا). هذا التحميل أعلى من الحمل الإضافي على النظام بسبب وجود عناصر <style> غير ضرورية على الصفحة. لكي أكون أمينًا ، لا يمكنني القول بثقة أن العلامات <style> غير الضرورية يمكن أن تسبب مشاكل في الأداء. يقومون ببساطة بتخزين الفئات غير المستخدمة التي تم إنشاؤها أثناء تشغيل الصفحة (أي ، لم يتم نقل هذه البيانات عبر الشبكة). ولكن يجب أن تعرف عن هذه الميزة لاستخدام تقنية CSS-in-JS.

يجب أن أقول إن علامات <style> لا تنشئ جميع مكتبات CSS-in-JS ، حيث لا تعتمد جميعها على الآليات التي تعمل عندما تعمل الصفحات في المتصفحات. على سبيل المثال ، لا تعمل مكتبة linaria على الإطلاق أثناء تشغيل الصفحة في المستعرض.

يقوم بتعريف مجموعة من فئات CSS الثابتة أثناء عملية إنشاء المشروع وضبط المراسلات لجميع القواعد الديناميكية في سلسلة القوالب (أي ، قواعد CSS اعتمادًا على خصائص المكون) مع خصائص CSS مخصصة. نتيجة لذلك ، عند تغيير خاصية مكون ، تتغير خاصية CSS ويتغير شكل الواجهة. بفضل هذا ، تعد linaria أسرع بكثير من المكتبات التي تعتمد على الآليات التي تعمل أثناء تشغيل الصفحات. الشيء هو أنه عند استخدام هذه المكتبة ، يجب على النظام إجراء حساب أقل بكثير أثناء تقديم المكون. الشيء الوحيد الذي عليك القيام به عند استخدام linaria أثناء العرض هو تذكر تحديث خاصية CSS المخصصة. ومع ذلك ، في الوقت نفسه ، لا يتوافق هذا الأسلوب مع IE11 ، ولديه دعم محدود لخصائص CSS الشائعة ، وبدون تكوين إضافي ، لا يسمح لك باستخدام السمات. كما هو الحال مع المجالات الأخرى لتطوير الويب ، لا يوجد في CSS-in-JS- مكتبات مثالية ، مناسبة لجميع المناسبات.

النتائج


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

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

فيما يلي بعض النصائح لتحسين أداء التطبيقات التي تستخدم مكتبات CSS-in-JS الشائعة التي تؤدي وظيفتها عند تشغيل الصفحات في مستعرض:

  1. لا تبتعد كثيراً عن تركيبة المكونات الأنيقة. حاول ألا تكرر الخطأ الذي تحدثت عنه في البداية ، ولا تحاول ، من أجل إنشاء زر مؤسف ، تكوين تركيبة مكونة من ثلاثة مكونات منمقة. إذا كنت تريد "إعادة استخدام" الكود ، فاستخدم خاصية CSS وأنشئ سلاسل قالب. سيتيح لك ذلك الاستغناء عن العديد من المكونات غير الضرورية لـ Context.Consumer . نتيجة لذلك ، سيتعين على React إدارة عدد أقل من المكونات ، مما سيزيد من إنتاجية المشروع.
  2. نسعى جاهدين لاستخدام مكونات "ثابتة". تعمل بعض مكتبات CSS-in-JS على تحسين التعليمة البرمجية التي تم إنشاؤها إذا كانت أنماط المكون لا تعتمد على السمة أو الخصائص. كلما كان هناك "ثابت" في سلاسل القوالب ، زاد احتمال تشغيل البرامج النصية في مكتبات CSS-in-JS بشكل أسرع.
  3. حاول تجنب عمليات إعادة العرض غير الضرورية لتطبيقات React الخاصة بك. نسعى جاهدين لتقديم فقط عندما كنت في حاجة إليها حقا. بفضل هذا ، لن يتم تحميل إجراءات React أو إجراءات مكتبة CSS-in-JS. إعادة العرض هي عملية يجب إجراؤها فقط في حالات استثنائية. على سبيل المثال - مع السحب المتزامن لعدد كبير من المكونات "الثقيلة".
  4. تعرف على ما إذا كانت مكتبة CSS-in-JS مناسبة لمشروعك الذي لا يستخدم البرامج النصية التي يتم تنفيذها أثناء تشغيل الصفحة في المستعرض. في بعض الأحيان نختار تقنية CSS-in-JS لأنها أكثر ملاءمة للمطور لاستخدامها ، وليست واجهات برمجة تطبيقات JavaScript مختلفة. ولكن إذا كان التطبيق الخاص بك لا يحتاج إلى دعم إذا لم يكن لديه استخدام مكثف لخصائص CSS ، فمن الممكن أنه يمكنك استخدام مكتبة CSS-in-JS مثل linaria التي لا تستخدم البرامج النصية التي تعمل أثناء تشغيل الصفحة. هذا النهج ، بالإضافة إلى ذلك ، سوف يقلل من حجم حزمة التطبيق بنحو 12 كيلو بايت. الحقيقة هي أن حجم رمز معظم مكتبات CSS-in-JS يناسب 12-15 كيلو بايت ، ورمز نفس linaria أقل من 1 كيلو بايت.

أعزائي القراء! هل تستخدم مكتبات CSS-in-JS؟


Source: https://habr.com/ru/post/ar480358/


All Articles