أفضل الممارسات المتعلقة بالعناصر المخصّصة

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

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

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

قائمة التحقق

Shadow DOM

أنشئ جذر ظل لتغليف الأنماط.

لماذا؟ إنّ تغليف الأنماط في جذر الظل للعنصر يضمن استمرار العمل بغض النظر عن مكان استخدامه. ويُعتبر ذلك أمرًا مهمًا على وجه الخصوص إذا أراد مطوّر البرامج وضع العنصر داخل جذر ظل لعنصر آخر. وينطبق ذلك حتى على العناصر البسيطة، مثل مربّع الاختيار أو زر الاختيار. قد يكون المحتوى الوحيد داخل جذر الظل هو الأنماط نفسها.
مثال العنصر <howto-checkbox>.

أنشئ جذر الظل في الدالة الإنشائية.

لماذا؟ تُستخدم الدالة الإنشائية عندما تكون لديك معرفة حصرية بعنصرك. ويُنصح بإعداد تفاصيل التنفيذ التي لا تريد أن تتعرّض لها عناصر أخرى. عند تنفيذ هذا الإجراء في معاودة الاتصال لاحقًا، مثل connectedCallback، يعني ذلك أنك ستحتاج إلى الحماية من المواقف التي يتم فيها فصل العنصر ثم إعادة إرفاقه بالمستند.
مثال العنصر <howto-checkbox>.

ضع أي عناصر فرعية ينشئها العنصر في جذر الظل الخاص به.

لماذا؟ تكون العناصر الثانوية التي أنشأها العنصر جزءًا من عملية التنفيذ ويجب أن تكون خاصة. وبدون حماية جذر الظل، قد تتداخل لغة JavaScript الخارجية بدون قصد مع هؤلاء الأطفال.
مثال العنصر <howto-tabs>.

استخدِم <slot> لعرض عناصر Light DOM الثانوية في shadow DOM

لماذا؟ يمكنك السماح لمستخدمي المكوِّن بتحديد المحتوى في المكوِّن لأن عناصر HTML الثانوية تجعله أكثر قابلية للتكوين. عندما لا يدعم المتصفح العناصر المخصصة، يظل المحتوى المتداخل متاحًا ومرئيًا ويمكن الوصول إليه.
مثال العنصر <howto-tabs>.

اضبط نمط عرض :host (مثل block أو inline-block أو flex) ما لم تفضِّل نمط العرض التلقائي inline.

لماذا؟ العناصر المخصّصة هي display: inline بشكل تلقائي، لذا لن يكون لضبط width أو height أي تأثير. وغالبًا ما يكون ذلك مفاجأة للمطوّرين وقد يتسبب في حدوث مشاكل متعلقة بتصميم الصفحة. ما لم تكن تفضّل عرض inline، يجب دائمًا ضبط قيمة display تلقائية.
مثال العنصر <howto-checkbox>.

أضِف نمط عرض :host يحترم السمة المخفية.

لماذا؟ سيتجاهل العنصر المخصّص ذو نمط display التلقائي، مثل :host { display: block }، سمة hidden المدمجة ذات الدقة الأقل. قد يفاجئك ذلك إذا كنت تتوقّع ضبط السمة hidden على العنصر لعرضه في display: none. بالإضافة إلى نمط display التلقائي، يمكنك إتاحة السمة hidden مع :host([hidden]) { display: none }.
مثال العنصر <howto-checkbox>.

السمات والخصائص

لا تتجاهل السمات العامة التي حدَّدها المؤلف.

لماذا؟ السمات العامة هي تلك التي تكون موجودة في جميع عناصر HTML. وتشمل بعض الأمثلة tabindex وrole. يمكن للعنصر المخصّص ضبط tabindex الأول على القيمة 0 ليصبح قابلاً للتركيز عليه من خلال لوحة المفاتيح. ولكن يجب دائمًا التحقق أولاً لمعرفة ما إذا كان المطوّر الذي يستخدم العنصر قد عيّن هذه القيمة على قيمة أخرى. على سبيل المثال، إذا تم ضبط tabindex على -1، هذه إشارة إلى أنّهم لا يريدون أن يكون العنصر تفاعليًا.
مثال العنصر <howto-checkbox>. ويمكنك الاطّلاع على مزيد من التفاصيل في القسم عدم تجاوز مؤلف الصفحة.

يجب دائمًا قبول البيانات الأساسية (السلاسل والأرقام والقيم المنطقية) كسمات أو خصائص.

لماذا؟ يجب أن تكون العناصر المخصصة، مثل نظيراتها المضمنة، قابلة للتهيئة. يمكن تمرير الإعدادات بشكل تعريفي، عبر السمات، أو تأكيديًا عبر خصائص JavaScript. من المفترَض أن تكون كل سمة مرتبطة أيضًا بسمة مقابلة.
مثال العنصر <howto-checkbox>.

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

لماذا؟ أنت لا تعرف أبدًا كيف سيتفاعل المستخدم مع عنصرك. قد يضبط الفريق سمة في JavaScript، ثم يتوقّع قراءة هذه القيمة باستخدام واجهة برمجة تطبيقات مثل getAttribute(). إذا كانت كل سمة لها سمة مطابقة يعكسها كلاهما، سيسهّل ذلك على المستخدمين التعامل مع العنصر الذي تقدّمه. بعبارة أخرى، عند استدعاء setAttribute('foo', value)، يجب أيضًا ضبط سمة foo مقابلة، والعكس صحيح. هناك بالطبع استثناءات لهذه القاعدة. يجب ألا تعرض خصائص التكرار العالي، مثل currentTime في مشغّل فيديو. استند إلى تقديرك الخاص. ننصحك بإجراء ذلك إذا بدا أنّ المستخدم سيتفاعل مع سمة أو سمة معيّنة، وليس من السهل توضيحها.
مثال العنصر <howto-checkbox>. ويمكن الاطّلاع على مزيد من التفاصيل في القسم تجنُّب مشاكل إعادة الدخول.

احرص على قبول البيانات المنسّقة (الكائنات والمصفوفات) كخصائص فقط.

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

لا تعكس سمات البيانات المنسّقة للسمات.

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

يمكنك البحث عن الخصائص التي ربما تم ضبطها قبل ترقية العنصر.

لماذا؟ قد يحاول مطوّر يستخدم عنصرك ضبط سمة على العنصر قبل أن يتم تحميل تعريفه. وينطبق ذلك على وجه الخصوص إذا كان المطوّر يستخدم إطار عمل يعالج مكونات التحميل وختمها بالصفحة وربط خصائصها بالنموذج.
مثال العنصر <howto-checkbox>. مزيد من التفاصيل في القسم جعل السمات "كسول".

لا تطبِّق صفوفًا بنفسك.

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

فعاليات

إرسال الأحداث استجابةً لنشاط المكوِّن الداخلي

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

لا ترسل الأحداث استجابةً لإعداد المضيف لموقع (تدفق البيانات التنازلي).

لماذا؟ وبالتالي، ليس من الضروري إرسال حدث استجابةً لإعداد مضيف (يعرف المضيف الحالة الحالية لأنه قد تم ضبطها للتو). قد يؤدي إرسال الأحداث استجابةً لإعداد مضيف لموقع إلى تكرارات لامتناهية مع أنظمة ربط البيانات.
مثال العنصر <howto-checkbox>.

التفسيرات

عدم تجاوز مؤلف الصفحة

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

connectedCallback() {
  if (!this.hasAttribute('role'))
    this.setAttribute('role', 'checkbox');
  if (!this.hasAttribute('tabindex'))
    this.setAttribute('tabindex', 0);

جعل المواقع الإلكترونية كسولة

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

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

<howto-checkbox [checked]="defaults.isChecked"></howto-checkbox>

يجب أن يتعامل العنصر المخصص مع هذا السيناريو عن طريق التحقق مما إذا كان قد تم بالفعل تعيين أي خصائص على المثيل الخاص به. توضح <howto-checkbox> هذا النمط باستخدام طريقة تسمى _upgradeProperty().

connectedCallback() {
  ...
  this._upgradeProperty('checked');
}

_upgradeProperty(prop) {
  if (this.hasOwnProperty(prop)) {
    let value = this[prop];
    delete this[prop];
    this[prop] = value;
  }
}

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

تجنُّب مشاكل الإقامة

من المغري استخدام attributeChangedCallback() لتعكس الحالة الخاصة بسمة أساسية، على سبيل المثال:

// When the [checked] attribute changes, set the checked property to match.
attributeChangedCallback(name, oldValue, newValue) {
  if (name === 'checked')
    this.checked = newValue;
}

ولكن يمكن أن يؤدي هذا إلى إنشاء حلقة لانهائية إذا عكست الخاصية setter أيضًا على السمة.

set checked(value) {
  const isChecked = Boolean(value);
  if (isChecked)
    // OOPS! This will cause an infinite loop because it triggers the
    // attributeChangedCallback() which then sets this property again.
    this.setAttribute('checked', '');
  else
    this.removeAttribute('checked');
}

وهناك حل بديل وهو السماح للدالة setter بالانعكاس على السمة، وجعل قيمة getter تُحدد قيمتها بناءً على السمة.

set checked(value) {
  const isChecked = Boolean(value);
  if (isChecked)
    this.setAttribute('checked', '');
  else
    this.removeAttribute('checked');
}

get checked() {
  return this.hasAttribute('checked');
}

في هذا المثال، ستؤدي إضافة السمة أو إزالتها إلى ضبط السمة أيضًا.

أخيرًا، يمكن استخدام attributeChangedCallback() لمعالجة الآثار الجانبية مثل تطبيق حالات ARIA.

attributeChangedCallback(name, oldValue, newValue) {
  const hasValue = newValue !== null;
  switch (name) {
    case 'checked':
      // Note the attributeChangedCallback is only handling the *side effects*
      // of setting the attribute.
      this.setAttribute('aria-checked', hasValue);
      break;
    ...
  }
}