كيف نبني مكونات واجهة المستخدم في Rails at Flywheel

نشرت: 2019-11-16

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

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

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

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

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


ما نحل من أجله

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

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

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

مثال مكون غير مقيد

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

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

.card__header
  .card__header-left
    %h2 Backups

  .card__header-right
    = link_to "#" do
      = icon("plus_small")

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

...
  .card__header-left
    = icon("arrow_backup", color: "gray25")
    %h2 Backups
...

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

...
  .card__header-left
    = icon("arrow_backup", color: "gray25")
    %h2.--ml-10 Backups
...

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

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

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

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

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

.card__header.--borderless
  .card__header-left
    %h2.--ml-10 Backups

  .card__header-right
    = link_to "#" do
      = icon("plus_small")

  %hr.--mt-0.--mb-0

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


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

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

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

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

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

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

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

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

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

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

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

مقدمة في الخلايا

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

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

%div
  %h1 Hello, world!

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

= cell("hello_world")

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

بناء الخلايا

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

تبدو الخلية الأساسية الفائقة بدون منطق فيها شيئًا كالتالي:

// cells/ui/slat_cell.rb

module UI
  class SlatCell < ViewModel
    def show
    end
  end
end

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

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

// cells/ui/slat/show.haml
.slat
  .slat__inner
    .slat__content
      %h4= options[:title]
      %p= options[:subtitle]
      = icon(options[:icon], color: "blue")

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

// cells/ui/slat_cell.rb
def title
  return unless options[:title]
  content_tag :h4, options[:title]
end

def subtitle
  return unless options[:subtitle]
  content_tag :p, options[:subtitle]
end
// cells/ui/slat/show.haml
.slat
  .slat__inner
    .slat__content
      = title
      = subtitle

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

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

= ui "slat", title: "Title", subtitle: "Subtitle", label: "Label"

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

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

= ui “slat", title: “Title”, subtitle: “Subtitle”, label: “Label”, link: “#”, tertiary_title: “Tertiary”, disabled: true, checklist: [“Item 1”, “Item 2”, “Item 3”]

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

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

module UI
  class SlatCell < ViewModel
    def show
      OptionProxy.new(self).yield!(options, &block)
      super()
    end
  end
end

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

= ui "slat" do |slat|
  - slat.title = "Title"
  - slat.subtitle = "Subtitle"
  - slat.label = "Label"
  - slat.link = "#"
  - slat.tertiary_title = "Tertiary"
  - slat.disabled = true
  - slat.checklist = ["Item 1", "Item 2", "Item 3"]

إدخال المنطق

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

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

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

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

def container(&block)
  tag =
    if options[:link]
      [:link_to, options[:link]]
    else
      [:content_tag, :div]
    end

  send(*tag, class: “slat__inner”, &block)
end

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

.slat
  = container do
  ...

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

= ui "slat" do |slat|
  ...
  - slat.disabled = true

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

.slat{ class: possible_classes("--disabled": options[:disabled]) }
  .slat__inner
    .slat__content
      %h4{ class: possible_classes("--alt": options[:disabled]) }= options[:title]
      %p{ class: possible_classes("--alt": options[:disabled]) }=
      options[:subtitle]
      = icon(options[:icon], color: "gray")

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

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


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

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

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

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


استنتاجنا

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

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

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


تعال للعمل في دولاب الموازنة!

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

جاهز للانضمام لفريقنا؟ نقوم بالتوظيف! قدم هنا.