कस्टम एलिमेंट के लिए सबसे सही तरीके

कस्टम एलिमेंट की मदद से, अपने एचटीएमएल टैग बनाए जा सकते हैं. इस चेकलिस्ट में, अच्छी क्वालिटी वाले एलिमेंट बनाने के सबसे सही तरीकों के बारे में बताया गया है.

कस्टम एलिमेंट की मदद से, एचटीएमएल को बढ़ाया जा सकता है और अपने टैग तय किए जा सकते हैं. वे बहुत ही असरदार सुविधा हैं, लेकिन उनका लेवल भी कम होता है. इसका मतलब है कि हमेशा ही यह पता नहीं होता कि अपने एलिमेंट को सबसे अच्छे तरीके से कैसे लागू किया जाए.

बेहतरीन अनुभव देने में आपकी मदद करने के लिए, हमने यह चेकलिस्ट बनाई है. यह उन सभी चीज़ों का विश्लेषण करता है जो एक अच्छी तरह से व्यवहार किए जाने वाला कस्टम एलिमेंट बनने के लिए हमें लगता है.

चेकलिस्ट

शैडो DOM

स्टाइल एनकैप्सुलेट करने के लिए, शैडो रूट बनाएं.

क्यों? अपने एलिमेंट के शैडो रूट में स्टाइल को एनकैप्सुलेट करने से, यह पक्का हो जाता है कि वे चाहे कहीं भी इस्तेमाल किए गए हों, वे हमेशा काम करें. यह खास तौर पर तब ज़रूरी होता है, जब कोई डेवलपर आपके एलिमेंट को किसी दूसरे एलिमेंट के शैडो रूट के अंदर रखना चाहता है. यह चेकबॉक्स या रेडियो बटन जैसे सामान्य एलिमेंट पर भी लागू होता है. ऐसा हो सकता है कि आपके शैडो रूट में सिर्फ़ स्टाइल वाला कॉन्टेंट हो.
उदाहरण <howto-checkbox> एलिमेंट.

कंस्ट्रक्टर में अपना शैडो रूट बनाएं.

क्यों? कंस्ट्रक्टर तब होता है, जब आपको अपने एलिमेंट के बारे में खास जानकारी मिलती है. इस सेटअप में ऐसी जानकारी शामिल करें जिसके साथ आपको किसी अन्य एलिमेंट के साथ गड़बड़ी नहीं करनी है. connectedCallback जैसे बाद के कॉलबैक में इस काम को करने का मतलब है कि आपको उन स्थितियों से बचने की ज़रूरत होगी जहां आपका एलिमेंट अलग होकर दस्तावेज़ से फिर से अटैच हो जाता है.
उदाहरण <howto-checkbox> एलिमेंट.

एलिमेंट से बनाए गए सभी बच्चों को अपने शैडो रूट में रखें.

क्यों? आपके एलिमेंट का इस्तेमाल करके बनाए गए बच्चों के लिए यह सुविधा लागू की जाती है. इसलिए, यह सुविधा निजी होनी चाहिए. शैडो रूट की सुरक्षा के बिना, JavaScript से बाहर की स्थितियों से अनजाने में इन बच्चों में रुकावट आ सकती है.
उदाहरण <howto-tabs> एलिमेंट.

लाइट DOM चाइल्ड को अपने शैडो DOM में प्रोजेक्ट करने के लिए, <स्लॉट> का इस्तेमाल करें

क्यों? यह विकल्प आपके कॉम्पोनेंट के उपयोगकर्ताओं को आपके कॉम्पोनेंट में कॉन्टेंट तय करने की अनुमति देता है, क्योंकि एचटीएमएल चिल्ड्रन आपके कॉम्पोनेंट को ज़्यादा कंपोज़ करने लायक बनाता है. जब कोई ब्राउज़र कस्टम एलिमेंट के साथ काम नहीं करता, तब भी नेस्ट किया गया कॉन्टेंट उपलब्ध रहता है, दिखता है, और ऐक्सेस किया जा सकता है.
उदाहरण <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 की डिफ़ॉल्ट स्टाइल के अलावा, :host([hidden]) { display: none } की मदद से hidden के लिए सहायता जोड़ें.
उदाहरण <howto-checkbox> एलिमेंट.

एट्रिब्यूट और प्रॉपर्टी

लेखक के सेट किए गए ग्लोबल एट्रिब्यूट को न बदलें.

क्यों? ग्लोबल एट्रिब्यूट वे होते हैं जो सभी एचटीएमएल एलिमेंट पर मौजूद होते हैं. इसके कुछ उदाहरण में tabindex और role शामिल हैं. कोई कस्टम एलिमेंट अपने शुरुआती tabindex को 0 पर सेट कर सकता है, ताकि उस पर कीबोर्ड फ़ोकस किया जा सके. हालांकि, आपको हमेशा पहले यह देखना चाहिए कि आपके एलिमेंट का इस्तेमाल करने वाले डेवलपर ने इसे किसी दूसरी वैल्यू पर सेट किया है या नहीं. उदाहरण के लिए, अगर उन्होंने tabindex को -1 पर सेट किया है, तो इसका मतलब है कि वे एलिमेंट को इंटरैक्टिव नहीं बनाना चाहते.
उदाहरण <howto-checkbox> एलिमेंट. इसके बारे में ज़्यादा जानकारी, पेज के लेखक को न बदलें.

हमेशा बुनियादी डेटा (स्ट्रिंग, संख्या, बूलियन) को एट्रिब्यूट या प्रॉपर्टी के तौर पर स्वीकार करें.

क्यों? कस्टम एलिमेंट, कॉन्फ़िगर किए जा सकते हैं, जैसे कि एलिमेंट पहले से मौजूद हैं. कॉन्फ़िगरेशन को, जानकारी के साथ, एट्रिब्यूट या JavaScript प्रॉपर्टी के ज़रिए ज़रूरी तौर पर पास किया जा सकता है. आम तौर पर, हर एट्रिब्यूट को उससे जुड़ी किसी प्रॉपर्टी से भी जोड़ा जाना चाहिए.
उदाहरण <howto-checkbox> एलिमेंट.

पुराने डेटा के एट्रिब्यूट और प्रॉपर्टी को सिंक में रखने की कोशिश करें. हर प्रॉपर्टी से एट्रिब्यूट को एक साथ दिखाने की कोशिश करें और सभी प्रॉपर्टी को एक-दूसरे से सिंक करके रखें.

क्यों? आपको कभी नहीं पता होता कि कोई उपयोगकर्ता आपके एलिमेंट से कैसे इंटरैक्ट करेगा. ऐसा हो सकता है कि वे JavaScript में कोई प्रॉपर्टी सेट करें और फिर getAttribute() जैसे एपीआई का इस्तेमाल करके, उस वैल्यू को पढ़ने की उम्मीद करें. अगर हर एट्रिब्यूट में उससे जुड़ी प्रॉपर्टी है और दोनों, दोनों दिखाते हैं, तो उपयोगकर्ता आपके एलिमेंट के साथ आसानी से काम कर पाएंगे. दूसरे शब्दों में, setAttribute('foo', value) को कॉल करने पर, उससे जुड़ी foo प्रॉपर्टी भी सेट की जानी चाहिए. इसी तरह, foo को कॉल करने पर, संबंधित प्रॉपर्टी भी सेट की जानी चाहिए. बेशक, इस नियम के अपवाद हैं. आपको वीडियो प्लेयर में, currentTime जैसी ज़्यादा फ़्रीक्वेंसी वाली प्रॉपर्टी नहीं दिखानी चाहिए. अपने विवेक का इस्तेमाल करें. अगर ऐसा लगता है कि कोई उपयोगकर्ता किसी प्रॉपर्टी या एट्रिब्यूट से इंटरैक्ट करेगा, और उसके बारे में बताना ज़्यादा बोझ नहीं है, तो ऐसा करें.
उदाहरण <howto-checkbox> एलिमेंट. ज़्यादा जानकारी, दोबारा सदस्यता से जुड़ी समस्याओं से बचें में दी गई है.

सिर्फ़ रिच डेटा (ऑब्जेक्ट, ऐरे) को प्रॉपर्टी के तौर पर स्वीकार करने की कोशिश करें.

क्यों? आम तौर पर, ऐसे बिल्ट-इन एचटीएमएल एलिमेंट का कोई उदाहरण नहीं है जो अपने एट्रिब्यूट के ज़रिए रिच डेटा (सामान्य JavaScript ऑब्जेक्ट और ऐरे) स्वीकार करते हैं. इसके बजाय, रिच डेटा को मेथड कॉल या प्रॉपर्टी के ज़रिए स्वीकार किया जाता है. रिच डेटा को एट्रिब्यूट के तौर पर स्वीकार करने पर, कुछ नुकसान भी हो सकता है: किसी स्ट्रिंग में किसी बड़े ऑब्जेक्ट को क्रम से लगाना काफ़ी महंगा हो सकता है. स्ट्रिंग बनाने की इस प्रोसेस में, ऑब्जेक्ट के सभी रेफ़रंस हट जाएंगे. उदाहरण के लिए, अगर किसी ऐसे ऑब्जेक्ट को स्ट्रिंग किया जाता है जिसमें किसी दूसरे ऑब्जेक्ट का रेफ़रंस या शायद डीओएम नोड है, तो वे रेफ़रंस हट जाएंगे.

एट्रिब्यूट में रिच डेटा प्रॉपर्टी न दिखाएं.

क्यों? रिच डेटा प्रॉपर्टी को एट्रिब्यूट के हिसाब से दिखाने में बहुत खर्चा आता है. इसके लिए, एक ही 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;
}

हालांकि, अगर प्रॉपर्टी सेटर भी एट्रिब्यूट को दिखाता है, तो इससे इनफ़ाइनाइट लूप बन सकता है.

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');
}

दूसरा विकल्प यह है कि प्रॉपर्टी सेटर को एट्रिब्यूट के हिसाब से वैल्यू दिखाने की अनुमति दी जाए. साथ ही, गेटर को एट्रिब्यूट के आधार पर उसकी वैल्यू तय करने दें.

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;
    ...
  }
}