نصائح وحيل لإنشاء مكونات واجهة مستخدم قابلة لإعادة الاستخدام

تصوير فرزارد ناصفي

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

الاهداف

ببساطة ، متطلبات بناء المكتبة هي كما يلي:

  1. يجب أن تكون منتجة.
  2. يجب أن تكون قابلة للصيانة.
  3. يجب أن تكون متسقة.

النهج

تقليل منطق العمل

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

قبل تنفيذ أي وظيفة ، من الجيد تحديد بعض الواجبات المسؤولة عن المكون.

تخيل أننا نبني مكون زر.

أود أن أكون قادرًا على:

  • إبلاغ أي نوع من زر هو - الابتدائية أو العادية
  • إبلاغ المحتوى المعروض داخل الزر (الرمز والنص)
  • تعطيل أو تمكين الزر
  • أداء بعض الإجراءات عند النقر

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

1 - النوع والمحتوى خاصان بالمكون ، بحيث يمكن وضعهما في ملف المكون.

نظرًا لأن النوع - إلى حد ما - مطلوب ، دعنا نضيف تحققًا في حالة عدم تقديم قيمة.

const type = get (هذا ، "النوع") ؛
أنواع const = {
  الأساسي: 'btn - Primary' ،
  العادية: "btn - العادية" ،
}
العودة (النوع)؟ type [type]: types.regular؛

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

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

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

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

منفصلة حالة قابلة لإعادة الاستخدام واجهة المستخدم

بعض تفاعلات واجهة المستخدم شائعة بين المكونات المختلفة ، مثل:

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

غالبًا ما تستخدم هذه الخصائص للتحكم في الحالة المرئية - كما نأمل.

الحفاظ على التسميات متسقة في جميع أنحاء مكونات مختلفة. يمكن نقل جميع الإجراءات المتعلقة بالحالة المرئية إلى مزيج.

/ * UIStateMixin * /
تعطيل() {
  اضبط (هذا ، "معطل" ، صحيح) ؛
  ارجع هذا
}،
تمكين () {
  ضبط (هذا ، 'معطل' ، خطأ ') ؛
  ارجع هذا
}،

كل طريقة مسؤولة فقط عن تبديل متغير معين وإرجاع السياق الحالي للتسلسل ، مثل:

زر
  .تعطيل()
  .showLoadingIndicator ()؛

يمكن تمديد هذا النهج. يمكنه قبول سياقات مختلفة والتحكم في المتغيرات الخارجية بدلاً من استخدام المتغيرات الداخلية. فمثلا:

_getCurrentDisabledAttr () {
  إرجاع (موجود (الحصول على (هذا ، "معطل")))
    ؟ "معطل" / * المعلمة الخارجية * /
    : 'معطل'؛ / * متغير داخلي * /
}،
تمكين (السياق) {
  تعيين (السياق || هذا ، this._getCurrentDisabledAttr () ، خطأ) ؛
  ارجع هذا
}

ملخص وظائف الأساس

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

يمكن أيضًا نقل هذه الطرق الافتراضية إلى مزجها ، مثل:

/ * BaseComponentMixin * /
_isCallbackValid (callbackName) {
  const callback = get (this، callbackName)؛
  
  return !! (isPresent (callback) && typeof callback === 'function')؛
}،
_handleCallback (رد اتصال ، params) {
  إذا (! this._isCallbackValid (رد اتصال)) {
    رمي خطأ جديد (/ * message * /) ؛
  }
  this.sendAction (رد الاتصال ، params) ؛
}،

ثم تدرج في المكونات.

/* مكون */
onClick (params) {
  this._handleCallback ('onClick' ، params) ؛
}

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

مكونات التأليف

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

فمثلا:

المكونات الأساسية: زر ، المنسدلة ، المدخلات.
زر القائمة المنسدلة => زر + القائمة المنسدلة
الإكمال التلقائي => الإدخال + المنسدلة
حدد => الإدخال (للقراءة فقط) + القائمة المنسدلة

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

فصل المخاوف في أفضل حالاتها.

تقسيم المخاوف

عند تكوين مكونات أكثر تعقيدًا ، فهناك إمكانية تقسيم المخاوف. يمكنك تقسيم المخاوف بين أجزاء مختلفة من المكون

دعنا نقول إننا نبني مكونًا محددًا.

{{form-select binding = productId items = items}}
العناصر = [
  {الوصف: 'المنتج # 1' ، القيمة: 1} ،
  {الوصف: 'المنتج # 2' ، القيمة: 2}
]

داخليًا ، لدينا مكون إدخال بسيط وقائمة منسدلة.

{{form-input binding = _description}}
{{ui-dropdown items = items onSelect = (action 'selectItem')}}

مهمتنا الرئيسية هي تقديم الوصف للمستخدم ، ولكن ليس له أي معنى لتطبيقنا - القيمة لا.

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

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

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

المسبقة مقابل مكونات جديدة

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

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

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

عندما لإنشاء المسبقة

1 - أنماط الاستخدام المتكرر

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

/ * التنفيذ المنتظم * /
{{شكل الإكمال التلقائي
    ملزمة = معرف المنتج
    url = "products" / * URL to be جلب * /
    labelAttr = "description" / * السمة المستخدمة كتسمية * /
    valueAttr = "id" / * السمة المستخدمة كقيمة * /
    apiAttr = "product" / * تم إرسال Param عند الطلب * /
}}
/ * المسبقة * /
{{شكل الإكمال التلقائي
    مسبقا = "المنتج"
    ملزمة = معرف المنتج
}}

يتم تعيين القيم من الإعداد المسبق فقط إذا لم يتم إبلاغ المعلمة ، والحفاظ على مرونته.

/ * تنفيذ ساذج وحدة المسبقة * /
الإعدادات المسبقة const = {
  المنتج: {
    عنوان url: "المنتجات" ،
    labelAttr: 'description' ،
    valueAttr: ‘id '،
    apiAttr: 'المنتج' ،
  }،
}
const attrs = presets [get (this، 'preset')]؛
Object.keys (attrs) .forEach ((prop) => {
  if (! get (this، prop)) {
    تعيين (هذا ، دعامة ، attrs [الدعامة]) ؛
  }
})؛

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

2 - المكون الأساسي معقد للغاية

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

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

عندما لإنشاء مكونات جديدة

1 - تمديد وظيفة

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

/* إعلان */
{{ui-button-dropdown items = items}}
/* تحت الغطاء */
{{# ui-button onClick = (الإجراء 'toggleDropdown')}}
  {{label}}  
{{/ واجهة المستخدم على زر}}
{{#if isExpanded}}
  {{ui-dropdown items = items}}
{{/إذا}}

يستخدم المثال أعلاه مكون الزر. يعمل هذا على توسيع تخطيطه لدعم رمز ثابت مع تضمين مكون منسدل وحالة الرؤية الخاصة به.

2 - تزيين المعلمات

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

/* إعلان */
{{form-datepicker onFocus = (action 'doSomething')}}
/* تحت الغطاء */
{{form-input onFocus = (action '_onFocus')}}
_مركز() {
  $ (this.element)
    .find ( 'إدخال')
    .تحديد()؛ / * حدد قيمة الحقل على التركيز * /
  this._handleCallback ( 'تشغيل OnFocus')؛ / * مشغلات رد الاتصال البارز * /
}

في هذا المثال ، تم توفيره للمكون وظيفة يُراد استدعاؤها عند تركيز الحقل.

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

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

في حالتي ، تم استبدال حدث onBlur بحدث آخر - onChange. يتم تشغيل هذا عندما يقوم المستخدم إما بملء الحقل أو تحديد تاريخ في التقويم.

استنتاج

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

أفضل نتيجة تأتي من كل فرد في المجموعة يقوم بما هو أفضل لنفسه وللجماعة - جون ناش

أيضًا ، لا تخجل من طلب التعليقات. ستجد دائمًا شيءًا يمكن العمل عليه.

لصقل مهاراتك في هندسة البرمجيات أكثر من ذلك ، أوصي باتباع سلسلة إريك إليوت "تأليف البرامج". إنه رائع!

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

أيضًا ، لا تتردد في التواصل معي على twittergcolombo_! أحب أن أسمع رأيك وحتى العمل معًا.

شكر!