كيف نبني مكونات واجهة المستخدم في ريلز

نشرت: 2024-06-28

يعد الحفاظ على الاتساق المرئي في تطبيق ويب كبير مشكلة مشتركة في العديد من المؤسسات. تم إنشاء تطبيق الويب الرئيسي وراء منتج Flywheel باستخدام Ruby on Rails، ولدينا العديد من مطوري Rails وثلاثة مطورين للواجهة الأمامية يلتزمون بتعليمات برمجية له في أي يوم محدد. نحن أيضًا متخصصون في التصميم (إنه أحد قيمنا الأساسية كشركة)، ولدينا ثلاثة مصممين يعملون بشكل وثيق جدًا مع المطورين في فرق Scrum لدينا.

يتعاون شخصان في تصميم موقع ويب

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

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

رجل ملتح يبتسم للكاميرا وهو جالس أمام شاشة الكمبيوتر التي تعرض سطورًا من التعليمات البرمجية

في هذا المقال سأتعمق في:

  • ما نحن حل ل
  • تقييد المكونات
  • تقديم المكونات على جانب الخادم
  • حيث لا يمكننا استخدام المكونات من جانب الخادم

ما الذي نحل من أجله

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

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

صفحة دليل النمط لمكون الشريحة

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

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

مثال على المكونات غير المقيدة

لتوضيح كيفية ظهور التناقضات بمرور الوقت، سأستخدم مثالًا بسيطًا (ومفتعلًا) ولكنه شائع جدًا لأحد مكوناتنا في تطبيق Flywheel: رؤوس البطاقات.

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

 .card__header
  .card__header-left
    %h2 النسخ الاحتياطية
  .card__header-يمين
    = link_to "#" افعل
      = أيقونة("plus_small")

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

 ...
  .card__header-left
    = أيقونة ("arrow_backup"، اللون: "رمادي 25")
    %h2 النسخ الاحتياطية
...

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

 ...
  .card__header-left
    = أيقونة ("arrow_backup"، اللون: "رمادي 25")
    %h2.--ml-10 نسخ احتياطية
...

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

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

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

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

مع كل هذه التعديلات على رأس البطاقة الأصلية، لدينا الآن فوضى في الكود.

 .card__header.--بلا حدود
  .card__header-left
    %h2.--ml-10 نسخ احتياطية
  .card__header-يمين
    = link_to "#" افعل
      = أيقونة("plus_small")
  %hr.--mt-0.--mb-0

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


تقييد المكونات

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

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

سنتناول كيفية بناء المكونات المقيدة أدناه، ولكن إليك بعض الفوائد التي وجدناها من خلال استخدامها:

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

تقديم المكونات على جانب الخادم

إذن ما الذي نتحدث عنه بتقييد المكونات؟ دعونا نحفر!

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

جزئيات للإنقاذ (أو هكذا اعتقدنا)

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

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

مقدمة إلى الخلايا

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

على المستوى الأبسط، تسمح لنا الخلايا بتجريد جزء من العلامات مثل هذا (نستخدم Haml في لغة القوالب الخاصة بنا):

 %ديف
  %h1 مرحباً بالعالم!

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

 = الخلية("hello_world")

يساعدنا هذا في النهاية على تقييد المكون حيث لا يمكن إضافة فئات مساعدة أو مكونات فرعية غير صحيحة دون تعديل الخلية نفسها.

بناء الخلايا

نضع جميع خلايا واجهة المستخدم الخاصة بنا في دليل التطبيق/الخلايا/واجهة المستخدم. يجب أن تحتوي كل خلية على ملف روبي واحد فقط، مُلحق بـ _cell.rb. يمكنك من الناحية الفنية كتابة القوالب مباشرة في روبي باستخدام مساعد content_tag، ولكن تحتوي معظم خلايانا أيضًا على قالب Haml المقابل الذي يوجد في مجلد مسمى بواسطة المكون.

تبدو الخلية الأساسية الفائقة التي لا تحتوي على أي منطق كما يلي:

 // الخلايا/ui/slat_cell.rb
واجهة المستخدم الوحدة النمطية
  فئة SlatCell <ViewModel
    عرض مواطنه
    نهاية
  نهاية
نهاية

التابع show هو ما يتم عرضه عند إنشاء مثيل للخلية وسيبحث تلقائيًا عن ملف show.haml المطابق في المجلد الذي يحمل نفس اسم الخلية. في هذه الحالة، يكون التطبيق/الخلايا/ui/slat (نقوم بتوسيع جميع خلايا واجهة المستخدم الخاصة بنا إلى وحدة واجهة المستخدم).

في القالب، يمكنك الوصول إلى الخيارات التي تم تمريرها إلى الخلية. على سبيل المثال، إذا تم إنشاء مثيل للخلية في عرض مثل = cell("ui/slat"، title: "Title"، عنوان فرعي: "Subtitle"، label: "Label")، يمكننا الوصول إلى هذه الخيارات من خلال كائن الخيارات.

 // الخلايا/ui/slat/show.haml
.عجيزة
  .slat__inner
    .slat__content
      %h4= الخيارات[:title]
      %p= الخيارات[:العنوان الفرعي]
      = أيقونة (خيارات [: أيقونة]، اللون: "الأزرق")

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

 // الخلايا/ui/slat_cell.rb
عنوان مواطنه
  العودة ما لم الخيارات[:title]
  content_tag :h4، الخيارات[:title]
نهاية
العنوان الفرعي بالتأكيد
  العودة ما لم الخيارات[:subtitle]
  content_tag :p، الخيارات[:subtitle]
نهاية
 // الخلايا/ui/slat/show.haml
.عجيزة
  .slat__inner
    .slat__content
      = عنوان
      = العنوان الفرعي

التفاف الخلايا باستخدام أداة واجهة المستخدم

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

 = واجهة المستخدم "الشريحة"، العنوان: "العنوان"، العنوان الفرعي: "العنوان الفرعي"، التسمية: "التسمية"

تمرير الخيارات ككتلة بدلاً من السطر

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

 = واجهة المستخدم "الشريحة"، العنوان: "العنوان"، العنوان الفرعي: "العنوان الفرعي"، التسمية: "التسمية"، الرابط: "#"، tertiary_title: "الثالثي"، معطل: صحيح، قائمة التحقق: ["العنصر 1"، "العنصر" 2"، "البند 3"]

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

فيما يلي مثال لاستخدام فئة OptionProxy داخل خليتنا:

 واجهة المستخدم الوحدة النمطية
  فئة SlatCell <ViewModel
    عرض مواطنه
      OptionProxy.new(self).yield!(خيارات، &حظر)
      ممتاز()
    نهاية
  نهاية
نهاية

الآن بعد أن تم تنفيذ ذلك، يمكننا تحويل خياراتنا المضمنة المرهقة إلى كتلة أكثر متعة!

 = ui "slat" قم بتنفيذ |slat|
  - slat.title = "العنوان"
  - slat.subtitle = "العنوان الفرعي"
  - slat.label = "التسمية"
  - slat.link = "#"
  - slat.tertiary_title = "المستوى الثالث"
  - slat.disabled = صحيح
  - slat.checklist = ["العنصر 1"، "العنصر 2"، "العنصر 3"]

التعريف بالمنطق

حتى هذه اللحظة، لم تتضمن الأمثلة أي منطق حول ما يعرضه العرض. هذا أحد أفضل الأشياء التي تقدمها Cells، لذا دعونا نتحدث عنها!

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

تستدعي الطريقة أدناه إما رابط link_to أو مساعد content_tag يعتمد على وجود الخيارات [:link] .

 حاوية Def(&كتلة)
  العلامة =
    إذا الخيارات[:رابط]
      [:link_to، الخيارات[:link]]
    آخر
      [:content_tag، :div]
    نهاية
  إرسال (*علامة، فئة: "slat__inner"، &block)
النهاية

يتيح لنا ذلك استبدال العنصر .slat__inner في القالب بكتلة حاوية:

 .عجيزة
  = حاوية تفعل
  ...

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

 = ui "slat" قم بتنفيذ |slat|
  ...
  - slat.disabled = صحيح

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

 .slat{ الفئة:احتمال_فئات("--تعطيل": الخيارات[:تعطيل]) }
  .slat__inner
    .slat__content
      %h4{ الفئة: محتمل_فئات("--alt": options[:disabled]) }= options[:title]
      %p{ class: محتمل_فئات("--alt": options[:disabled]) }=
      الخيارات[:العنوان الفرعي]
      = أيقونة (خيارات [: أيقونة]، اللون: "رمادي")

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

ملاحظة: الإمكانية_الفئات هي طريقة قمنا بإنشائها للسماح بالتطبيق المشروط للفئات في هامل بطريقة لطيفة.


حيث لا يمكننا استخدام مكونات جانب الخادم

على الرغم من أن نهج الخلية مفيد للغاية لتطبيقنا الخاص والطريقة التي نعمل بها، إلا أنني سأكون مقصرا إذا قلت إنه حل 100% من مشاكلنا. ما زلنا نكتب JavaScript (الكثير منها) ونبني عددًا لا بأس به من التجارب في Vue عبر تطبيقنا. في 75% من الحالات، لا يزال قالب Vue الخاص بنا موجودًا في Haml ونربط مثيلات Vue الخاصة بنا بالعنصر المحتوي، مما يسمح لنا بالاستمرار في الاستفادة من نهج الخلية.

ومع ذلك، في الأماكن التي يكون من المنطقي فيها تقييد أحد المكونات تمامًا كمثيل Vue ذو ملف واحد، لا يمكننا استخدام الخلايا. قوائمنا المختارة، على سبيل المثال، كلها Vue. لكن أعتقد أن هذا جيد! لم نواجه حقًا الحاجة إلى وجود إصدارات مكررة من المكونات في كل من مكونات Cells وVue، لذلك لا بأس أن تكون بعض المكونات مبنية بنسبة 100% باستخدام Vue وبعضها الآخر باستخدام Cells.

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

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


استنتاجنا

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

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

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


تعال واعمل معنا!

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

هل أنت مستعد للانضمام إلى فريقنا؟ نقوم بالتوظيف! قدم هنا.