वेब कॉम्पोनेंट से Lit Element तक

1. परिचय

पिछले अपडेट की तारीख: 10-08-2021

वेब कॉम्पोनेंट

वेब कॉम्पोनेंट, वेब प्लैटफ़ॉर्म एपीआई का एक सेट है. इसकी मदद से, नए कस्टम, दोबारा इस्तेमाल किए जा सकने वाले, और इनकैप्सुलेटेड एचटीएमएल टैग बनाए जा सकते हैं. इनका इस्तेमाल वेब पेज और वेब ऐप्लिकेशन में किया जा सकता है. वेब कॉम्पोनेंट के स्टैंडर्ड पर बनाए गए कस्टम कॉम्पोनेंट और विजेट, मॉडर्न ब्राउज़र पर काम करेंगे. साथ ही, इनका इस्तेमाल किसी भी ऐसी JavaScript लाइब्रेरी या फ़्रेमवर्क के साथ किया जा सकता है जो एचटीएमएल के साथ काम करता है.

Lit क्या है

Lit एक आसान लाइब्रेरी है. इसकी मदद से, तेज़ और हल्के वेब कॉम्पोनेंट बनाए जा सकते हैं. ये कॉम्पोनेंट किसी भी फ़्रेमवर्क में काम करते हैं. इसके अलावा, इन्हें बिना किसी फ़्रेमवर्क के भी इस्तेमाल किया जा सकता है. Lit की मदद से, शेयर किए जा सकने वाले कॉम्पोनेंट, ऐप्लिकेशन, डिज़ाइन सिस्टम वगैरह बनाए जा सकते हैं.

Lit, सामान्य वेब कॉम्पोनेंट टास्क को आसान बनाने के लिए एपीआई उपलब्ध कराता है. जैसे, प्रॉपर्टी, एट्रिब्यूट, और रेंडरिंग को मैनेज करना.

आपको क्या सीखने को मिलेगा

  • वेब कॉम्पोनेंट क्या है
  • वेब कॉम्पोनेंट के कॉन्सेप्ट
  • वेब कॉम्पोनेंट बनाने का तरीका
  • lit-html और LitElement क्या हैं
  • Lit, वेब कॉम्पोनेंट के ऊपर क्या करता है

आपको क्या बनाने को मिलेगा

  • वैनिला थंब्स अप / डाउन वेब कॉम्पोनेंट
  • Lit पर आधारित वेब कॉम्पोनेंट, जिसमें पसंद / नापसंद करने का विकल्प होता है

आपको इन चीज़ों की ज़रूरत होगी

  • अपडेट किया गया कोई भी आधुनिक ब्राउज़र (Chrome, Safari, Firefox, Chromium Edge). वेब कॉम्पोनेंट, सभी मॉडर्न ब्राउज़र में काम करते हैं. साथ ही, Microsoft Internet Explorer 11 और नॉन-क्रोमियम Microsoft Edge के लिए पॉलीफ़िल उपलब्ध हैं.
  • एचटीएमएल, सीएसएस, JavaScript, और Chrome DevTools की जानकारी.

2. Playground को सेट अप करना और उसे एक्सप्लोर करना

कोड ऐक्सेस करना

इस कोडलैब में, Lit के प्लेग्राउंड के लिंक इस तरह दिए गए होंगे:

प्लेग्राउंड एक कोड सैंडबॉक्स है, जो पूरी तरह से आपके ब्राउज़र में चलता है. यह TypeScript और JavaScript फ़ाइलों को कंपाइल और चला सकता है. साथ ही, यह नोड मॉड्यूल में इंपोर्ट की गई फ़ाइलों को अपने-आप रिज़ॉल्व भी कर सकता है. उदाहरण के लिए,

// before
import './my-file.js';
import 'lit';

// after
import './my-file.js';
import 'https://unpkg.com/lit?module';

इस पूरे ट्यूटोरियल को Lit playground में किया जा सकता है. इसके लिए, इन चेकपॉइंट को शुरुआती पॉइंट के तौर पर इस्तेमाल करें. अगर VS Code का इस्तेमाल किया जा रहा है, तो इन चेकपॉइंट का इस्तेमाल करके, किसी भी चरण के लिए शुरुआती कोड डाउनलोड किया जा सकता है. साथ ही, इनका इस्तेमाल अपने काम की जांच करने के लिए भी किया जा सकता है.

लाइट वाले प्लेग्राउंड के यूज़र इंटरफ़ेस (यूआई) के बारे में जानकारी

फ़ाइल चुनने वाले टैब बार को सेक्शन 1, कोड में बदलाव करने वाले सेक्शन को सेक्शन 2, आउटपुट की झलक को सेक्शन 3, और झलक को फिर से लोड करने वाले बटन को सेक्शन 4 के तौर पर लेबल किया गया है

इस इमेज में Lit playground के यूज़र इंटरफ़ेस का स्क्रीनशॉट दिखाया गया है. इसमें उन सेक्शन को हाइलाइट किया गया है जिनका इस्तेमाल इस कोडलैब में किया जाएगा.

  1. फ़ाइल चुनने वाला टूल. प्लस बटन पर ध्यान दें...
  2. फ़ाइल एडिटर.
  3. कोड की झलक.
  4. 'फिर से लोड करें' बटन.
  5. 'डाउनलोड करें' बटन.

VS Code सेटअप करना (ऐडवांस)

VS Code के इस सेटअप का इस्तेमाल करने के ये फ़ायदे हैं:

  • टेंप्लेट टाइप की जांच करना
  • टेंप्लेट के लिए इंटेलिसेंस और अपने-आप पूरा होने की सुविधा

अगर आपने पहले से ही NPM और VS Code (lit-plugin प्लग इन के साथ) इंस्टॉल कर लिया है और आपको उस एनवायरमेंट का इस्तेमाल करना आता है, तो इन प्रोजेक्ट को डाउनलोड करके शुरू किया जा सकता है. इसके लिए, यह तरीका अपनाएं:

  • 'डाउनलोड करें' बटन दबाएं
  • टार फ़ाइल के कॉन्टेंट को किसी डायरेक्ट्री में एक्सट्रैक्ट करना
  • एक ऐसा डेवलपमेंट सर्वर इंस्टॉल करें जो बेयर मॉड्यूल स्पेसिफ़ायर को हल कर सके. Lit टीम, @web/dev-server को इस्तेमाल करने का सुझाव देती है
    • यहां एक उदाहरण दिया गया है package.json
  • डेवलपमेंट सर्वर चलाएं और अपना ब्राउज़र खोलें. अगर @web/dev-server का इस्तेमाल किया जा रहा है, तो npx web-dev-server --node-resolve --watch --open का इस्तेमाल किया जा सकता है
    • अगर उदाहरण package.json का इस्तेमाल किया जा रहा है, तो npm run serve का इस्तेमाल करें

3. कस्टम एलिमेंट तय करना

कस्टम एलिमेंट

वेब कॉम्पोनेंट, चार नेटिव वेब एपीआई का कलेक्शन है. कैंपेन के तीनों सब-टाइप के नाम ये रहे:

  • ES मॉड्यूल
  • कस्टम एलिमेंट
  • शैडो डीओएम
  • एचटीएमएल टेंप्लेट

आपने पहले ही ES मॉड्यूल स्पेसिफ़िकेशन का इस्तेमाल किया है. इससे इंपोर्ट और एक्सपोर्ट के साथ JavaScript मॉड्यूल बनाए जा सकते हैं. इन्हें <script type="module"> की मदद से पेज में लोड किया जाता है.

कस्टम एलिमेंट को तय करना

कस्टम एलिमेंट स्पेसिफ़िकेशन की मदद से, उपयोगकर्ता JavaScript का इस्तेमाल करके अपने एचटीएमएल एलिमेंट तय कर सकते हैं. इनके नामों में हाइफ़न (-) होना चाहिए, ताकि इन्हें नेटिव ब्राउज़र एलिमेंट से अलग किया जा सके. index.js फ़ाइल को हटाएं और कस्टम एलिमेंट क्लास तय करें:

index.js

class RatingElement extends HTMLElement {}

customElements.define('rating-element', RatingElement);

कस्टम एलिमेंट को इस तरह से तय किया जाता है: HTMLElement को बढ़ाने वाली क्लास को हाइफ़न वाले टैग के नाम से जोड़कर. customElements.define को कॉल करने पर, ब्राउज़र को क्लास RatingElement को tagName ‘rating-element' से जोड़ने के लिए कहा जाता है. इसका मतलब है कि आपके दस्तावेज़ में <rating-element> नाम वाला हर एलिमेंट, इस क्लास से जुड़ा होगा.

दस्तावेज़ के मुख्य हिस्से में <rating-element> डालें और देखें कि क्या रेंडर होता है.

index.html

<body>
 <rating-element></rating-element>
</body>

अब आउटपुट देखने पर, आपको पता चलेगा कि कुछ भी रेंडर नहीं हुआ है. ऐसा इसलिए हो रहा है, क्योंकि आपने ब्राउज़र को यह नहीं बताया है कि <rating-element> को कैसे रेंडर करना है. इस बात की पुष्टि की जा सकती है कि कस्टम एलिमेंट की परिभाषा सही तरीके से लागू हुई है या नहीं. इसके लिए, Chrome DevTools के एलिमेंट सिलेक्टर में <rating-element> को चुनें. इसके बाद, कंसोल में यह कॉल करें:

$0.constructor

इससे यह आउटपुट मिलना चाहिए:

class RatingElement extends HTMLElement {}

कस्टम एलिमेंट की लाइफ़साइकल

कस्टम एलिमेंट में लाइफ़साइकल हुक का सेट होता है. कैंपेन के तीनों सब-टाइप के नाम ये रहे:

  • constructor
  • connectedCallback
  • disconnectedCallback
  • attributeChangedCallback
  • adoptedCallback

जब एलिमेंट पहली बार बनाया जाता है, तब constructor को कॉल किया जाता है. उदाहरण के लिए, document.createElement(‘rating-element') या new RatingElement() को कॉल करके. कंस्ट्रक्टर, एलिमेंट को सेट अप करने के लिए एक अच्छी जगह है. हालांकि, एलिमेंट को "बूट-अप" करने की परफ़ॉर्मेंस से जुड़ी वजहों के लिए, कंस्ट्रक्टर में DOM में बदलाव करना आम तौर पर सही तरीका नहीं माना जाता.

connectedCallback को तब कॉल किया जाता है, जब कस्टम एलिमेंट को DOM से अटैच किया जाता है. आम तौर पर, शुरुआती DOM में बदलाव यहीं होते हैं.

disconnectedCallback को तब कॉल किया जाता है, जब कस्टम एलिमेंट को डीओएम से हटा दिया जाता है.

उपयोगकर्ता की ओर से तय किए गए किसी भी एट्रिब्यूट में बदलाव होने पर, attributeChangedCallback(attrName, oldValue, newValue) को कॉल किया जाता है.

adoptedCallback को तब कॉल किया जाता है, जब कस्टम एलिमेंट को adoptNode के ज़रिए किसी दूसरे documentFragment से मुख्य दस्तावेज़ में शामिल किया जाता है. जैसे, HTMLTemplateElement में.

DOM रेंडर करें

अब, कस्टम एलिमेंट पर वापस जाएं और उससे कुछ डीओएम को जोड़ें. जब एलिमेंट DOM से अटैच हो जाए, तब उसका कॉन्टेंट सेट करें:

index.js

class RatingElement extends HTMLElement {
 constructor() {
   super();
   this.rating = 0;
 }
 connectedCallback() {
   this.innerHTML = `
     <style>
       rating-element {
         display: inline-flex;
         align-items: center;
       }
       rating-element button {
         background: transparent;
         border: none;
         cursor: pointer;
       }
     </style>
     <button class="thumb_down" >
       <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
     </button>
     <span class="rating">${this.rating}</span>
     <button class="thumb_up">
       <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
     </button>
   `;
 }
}

customElements.define('rating-element', RatingElement);

constructor में, एलिमेंट पर rating नाम की इंस्टेंस प्रॉपर्टी सेव की जाती है. connectedCallback में, <rating-element> में DOM चाइल्ड जोड़े जाते हैं, ताकि मौजूदा रेटिंग के साथ-साथ 'पसंद करें' और 'नापसंद करें' बटन दिखाए जा सकें.

4. शैडो डीओएम

शैडो डीओएम क्यों इस्तेमाल करें?

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

index.html

<!DOCTYPE html>
<html>
 <head>
   <script src="./index.js" type="module"></script>
   <style>
     span {
       border: 1px solid red;
     }
   </style>
 </head>
 <body>
   <rating-element></rating-element>
 </body>
</html>

आपके जवाब में, रेटिंग के स्पैन के चारों ओर लाल रंग का बॉर्डर बॉक्स होना चाहिए. यह एक सामान्य मामला है. हालांकि, DOM इनकैप्सुलेशन न होने की वजह से, ज़्यादा जटिल ऐप्लिकेशन में बड़ी समस्याएं आ सकती हैं. ऐसे में, शैडो DOM काम आता है.

शैडो रूट अटैच करना

एलिमेंट में शैडो रूट अटैच करें और उस रूट के अंदर डीओएम रेंडर करें:

index.js

class RatingElement extends HTMLElement {
 constructor() {
   super();
   this.rating = 0;
 }
 connectedCallback() {
   const shadowRoot = this.attachShadow({mode: 'open'});

   shadowRoot.innerHTML = `
     <style>
       :host {
         display: inline-flex;
         align-items: center;
       }
       button {
         background: transparent;
         border: none;
         cursor: pointer;
       }
     </style>
     <button class="thumb_down" >
       <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
     </button>
     <span class="rating">${this.rating}</span>
     <button class="thumb_up">
       <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
     </button>
   `;
 }
}

customElements.define('rating-element', RatingElement);

पेज को रीफ़्रेश करने पर, आपको दिखेगा कि मुख्य दस्तावेज़ में मौजूद स्टाइल, शैडो रूट के अंदर मौजूद नोड को अब नहीं चुन सकती हैं.

आपने यह कैसे किया? आपने connectedCallback को कॉल किया है this.attachShadow, जो किसी एलिमेंट से शैडो रूट अटैच करता है. open मोड का मतलब है कि शैडो कॉन्टेंट की जांच की जा सकती है. साथ ही, this.shadowRoot की मदद से शैडो रूट को भी ऐक्सेस किया जा सकता है. Chrome Inspector में भी वेब कॉम्पोनेंट देखें:

Chrome इंस्पेक्टर में डीओएम ट्री. यहां एक <rating-element> है, जिसमें#shadow-root (open) चाइल्ड के तौर पर है. साथ ही, उस शैडरूट में पहले से मौजूद DOM है.

अब आपको एक बड़ा किया जा सकने वाला शैडो रूट दिखेगा, जिसमें कॉन्टेंट मौजूद होगा. उस शैडो रूट के अंदर मौजूद हर चीज़ को शैडो DOM कहा जाता है. अगर आपको Chrome Dev Tools में रेटिंग एलिमेंट चुनना है और $0.children को कॉल करना है, तो आपको दिखेगा कि यह कोई भी चाइल्ड एलिमेंट नहीं दिखाता है. ऐसा इसलिए है, क्योंकि शैडो डीओएम को डायरेक्ट चाइल्ड की तरह एक ही डीओएम ट्री का हिस्सा नहीं माना जाता. इसके बजाय, इसे शैडो ट्री का हिस्सा माना जाता है.

लाइट डीओएम

एक्सपेरिमेंट: <rating-element> के डायरेक्ट चाइल्ड के तौर पर कोई नोड जोड़ें:

index.html

<rating-element>
 <div>
   This is the light DOM!
 </div>
</rating-element>

पेज को रीफ़्रेश करें. आपको दिखेगा कि इस कस्टम एलिमेंट के लाइट DOM में मौजूद यह नया DOM नोड, पेज पर नहीं दिखता. ऐसा इसलिए है, क्योंकि शैडो डीओएम में ऐसी सुविधाएं होती हैं जिनसे यह कंट्रोल किया जा सकता है कि <slot> एलिमेंट के ज़रिए, लाइट डीओएम नोड को शैडो डीओएम में कैसे प्रोजेक्ट किया जाए.

5. एचटीएमएल टेंप्लेट

टेंप्लेट क्यों

innerHTML और टेंप्लेट लिटरल स्ट्रिंग का इस्तेमाल करने से, स्क्रिप्ट इंजेक्शन से जुड़ी सुरक्षा समस्याएं हो सकती हैं. पहले, DocumentFragment का इस्तेमाल किया जाता था. हालांकि, इनमें भी कई समस्याएं आती हैं. जैसे, टेंप्लेट तय किए जाने पर इमेज लोड होना और स्क्रिप्ट चलना. साथ ही, इनका दोबारा इस्तेमाल करने में भी समस्याएं आती हैं. यहां <template> एलिमेंट काम आता है. टेंप्लेट, इनर्ट डीओएम उपलब्ध कराते हैं. यह नोड को क्लोन करने का एक बेहतर तरीका है. साथ ही, इसमें टेंप्लेट का दोबारा इस्तेमाल किया जा सकता है.

टेंप्लेट का इस्तेमाल करना

इसके बाद, कॉम्पोनेंट को एचटीएमएल टेंप्लेट का इस्तेमाल करने के लिए ट्रांज़िशन करें:

index.html

<body>
 <template id="rating-element-template">
   <style>
     :host {
       display: inline-flex;
       align-items: center;
     }
     button {
       background: transparent;
       border: none;
       cursor: pointer;
     }
   </style>
   <button class="thumb_down" >
     <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
   </button>
   <span class="rating"></span>
   <button class="thumb_up">
     <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
   </button>
 </template>

 <rating-element>
   <div>
     This is the light DOM!
   </div>
 </rating-element>
</body>

यहां आपने मुख्य दस्तावेज़ के डीओएम में, डीओएम कॉन्टेंट को टेंप्लेट टैग में ले जाया है. अब कस्टम एलिमेंट की डेफ़िनिशन को फिर से व्यवस्थित करें:

index.js

class RatingElement extends HTMLElement {
 constructor() {
   super();
   this.rating = 0;
 }
 connectedCallback() {
   const shadowRoot = this.attachShadow({mode: 'open'});
   const templateContent = document.getElementById('rating-element-template').content;
   const clonedContent = templateContent.cloneNode(true);
   shadowRoot.appendChild(clonedContent);

   this.shadowRoot.querySelector('.rating').innerText = this.rating;
 }
}

customElements.define('rating-element', RatingElement);

इस टेंप्लेट एलिमेंट का इस्तेमाल करने के लिए, टेंप्लेट को क्वेरी करें, उसका कॉन्टेंट पाएं, और उन नोड को templateContent.cloneNode के साथ क्लोन करें. यहां templateContent.cloneNode आर्ग्युमेंट, डीप क्लोन करता है.true इसके बाद, डेटा की मदद से DOM को शुरू करें.

बधाई हो, अब आपके पास एक वेब कॉम्पोनेंट है! माफ़ करें, फ़िलहाल यह कुछ नहीं करता. इसलिए, इसमें कुछ फ़ंक्शन जोड़ें.

6. सुविधाएं जोड़ना

प्रॉपर्टी बाइंडिंग

फ़िलहाल, रेटिंग-एलिमेंट पर रेटिंग सेट करने का सिर्फ़ एक तरीका है. इसके लिए, एलिमेंट बनाएं, ऑब्जेक्ट पर rating प्रॉपर्टी सेट करें, और फिर उसे पेज पर रखें. माफ़ करें, नेटिव एचटीएमएल एलिमेंट इस तरह से काम नहीं करते. नेटिव एचटीएमएल एलिमेंट, प्रॉपर्टी और एट्रिब्यूट, दोनों में हुए बदलावों के साथ अपडेट होते हैं.

rating प्रॉपर्टी में बदलाव होने पर, व्यू को अपडेट करने के लिए कस्टम एलिमेंट बनाएं. इसके लिए, यहां दी गई लाइनें जोड़ें:

index.js

constructor() {
  super();
  this._rating = 0;
}

set rating(value) {
  this._rating = value;
  if (!this.shadowRoot) {
    return;
  }

  const ratingEl = this.shadowRoot.querySelector('.rating');
  if (ratingEl) {
    ratingEl.innerText = this._rating;
  }
}

get rating() {
  return this._rating;
}

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

एट्रिब्यूट बाइंडिंग

अब एट्रिब्यूट में बदलाव होने पर व्यू को अपडेट करें. यह उस इनपुट की तरह ही है जो <input value="newValue"> सेट करने पर अपने व्यू को अपडेट करता है. खुशी की बात यह है कि वेब कॉम्पोनेंट के लाइफ़साइकल में attributeChangedCallback शामिल है. यहां दी गई लाइनों को जोड़कर रेटिंग अपडेट करें:

index.js

static get observedAttributes() {
 return ['rating'];
}

attributeChangedCallback(attributeName, oldValue, newValue) {
 if (attributeName === 'rating') {
   const newRating = Number(newValue);
   this.rating = newRating;
 }
}

attributeChangedCallback को ट्रिगर करने के लिए, आपको RatingElement.observedAttributes which defines the attributes to be observed for changes के लिए स्टैटिक गेटर सेट करना होगा. इसके बाद, DOM में रेटिंग सेट की जाती है. इसे आज़माएं:

index.html

<rating-element rating="5"></rating-element>

अब रेटिंग को एलान के तौर पर अपडेट किया जाना चाहिए!

बटन की सुविधा

अब सिर्फ़ बटन की सुविधा काम नहीं कर रही है. इस कॉम्पोनेंट का व्यवहार ऐसा होना चाहिए कि उपयोगकर्ता, रेटिंग को सिर्फ़ एक बार 'पसंद' या 'नापसंद' कर सके. साथ ही, उपयोगकर्ता को विज़ुअल फ़ीडबैक मिल सके. इसे कुछ इवेंट लिसनर और रिफ़्लेक्टिंग प्रॉपर्टी के साथ लागू किया जा सकता है. हालांकि, इससे पहले स्टाइल अपडेट करें, ताकि ये लाइनें जोड़कर विज़ुअल फ़ीडबैक दिया जा सके:

index.html

<style>
...

 :host([vote=up]) .thumb_up {
   fill: green;
 }
  :host([vote=down]) .thumb_down {
   fill: red;
 }
</style>

शैडो डीओएम में, :host सिलेक्टर उस नोड या कस्टम एलिमेंट को दिखाता है जिससे शैडो रूट जुड़ा होता है. इस मामले में, अगर vote एट्रिब्यूट की वैल्यू "up" है, तो 'पसंद करें' बटन का रंग हरा हो जाएगा. हालांकि, अगर vote एट्रिब्यूट की वैल्यू "down", then it will turn the thumb-down button red है. अब इसके लिए लॉजिक लागू करें. इसके लिए, vote के लिए एक रिफ़्लेक्टिंग प्रॉपर्टी / एट्रिब्यूट बनाएं. इसे उसी तरह लागू करें जिस तरह आपने rating को लागू किया था. प्रॉपर्टी सेटर और गेटर से शुरू करें:

index.js

constructor() {
  super();
  this._rating = 0;
  this._vote = null;
}

set vote(newValue) {
  const oldValue = this._vote;
  if (newValue === oldValue) {
    return;
  }
  if (newValue === 'up') {
    if (oldValue === 'down') {
      this.rating += 2;
    } else {
      this.rating += 1;
    }
  } else if (newValue === 'down') {
    if (oldValue === 'up') {
      this.rating -= 2;
    } else {
      this.rating -= 1;
    }
  }
  this._vote = newValue;
  this.setAttribute('vote', newValue);
}

get vote() {
  return this._vote;
}

आपने constructor में null की मदद से _vote इंस्टेंस प्रॉपर्टी को शुरू किया है. साथ ही, सेटर में यह देखा है कि नई वैल्यू अलग है या नहीं. अगर ऐसा है, तो रेटिंग को इसके मुताबिक अडजस्ट करें. साथ ही, vote एट्रिब्यूट की वैल्यू को this.setAttribute एट्रिब्यूट के साथ होस्ट को वापस भेजें.

इसके बाद, एट्रिब्यूट बाइंडिंग सेट अप करें:

index.js

static get observedAttributes() {
  return ['rating', 'vote'];
}

attributeChangedCallback(attributeName, oldValue, newValue) {
  if (attributeName === 'rating') {
    const newRating = Number(newValue);

    this.rating = newRating;
  } else if (attributeName === 'vote') {
    this.vote = newValue;
  }
}

यह वही प्रोसेस है जिसे आपने rating एट्रिब्यूट बाइंडिंग के दौरान इस्तेमाल किया था. आपको observedAttributes में vote जोड़ना होगा और attributeChangedCallback में vote प्रॉपर्टी सेट करनी होगी. अब आखिर में, बटन को काम करने की सुविधा देने के लिए, कुछ क्लिक इवेंट लिसनर जोड़ें!

index.js

constructor() {
 super();
 this._rating = 0;
 this._vote = null;
 this._boundOnUpClick = this._onUpClick.bind(this);
 this._boundOnDownClick = this._onDownClick.bind(this);
}

connectedCallback() {
  ...
  this.shadowRoot.querySelector('.thumb_up')
    .addEventListener('click', this._boundOnUpClick);
  this.shadowRoot.querySelector('.thumb_down')
    .addEventListener('click', this._boundOnDownClick);
}

disconnectedCallback() {
  this.shadowRoot.querySelector('.thumb_up')
    .removeEventListener('click', this._boundOnUpClick);
  this.shadowRoot.querySelector('.thumb_down')
    .removeEventListener('click', this._boundOnDownClick);
}

_onUpClick() {
  this.vote = 'up';
}

_onDownClick() {
  this.vote = 'down';
}

constructor में, एलिमेंट से कुछ क्लिक लिसनर बाइंड किए जाते हैं और रेफ़रंस को सेव रखा जाता है. connectedCallback में, बटन पर क्लिक इवेंट सुने जाते हैं. disconnectedCallback में, इन श्रोताओं को साफ़ किया जाता है. साथ ही, श्रोताओं के क्लिक करने पर, vote को सही तरीके से सेट किया जाता है.

बधाई हो, अब आपके पास पूरी सुविधाओं वाला वेब कॉम्पोनेंट है. कुछ बटन पर क्लिक करके देखें! अब समस्या यह है कि मेरी JS फ़ाइल में 96 लाइनें हैं, मेरी एचटीएमएल फ़ाइल में 43 लाइनें हैं, और कोड काफ़ी बड़ा है. साथ ही, इस तरह के सामान्य कॉम्पोनेंट के लिए यह ज़रूरी है. ऐसे में, Google का Lit प्रोजेक्ट आपकी मदद कर सकता है!

7. Lit-html

कोड चेकपॉइंट

lit-html का इस्तेमाल क्यों करें

सबसे पहले और सबसे ज़रूरी बात यह है कि <template> टैग काम का है और अच्छी परफ़ॉर्मेंस देता है. हालांकि, इसे कॉम्पोनेंट के लॉजिक के साथ पैकेज नहीं किया जाता है. इसलिए, टेंप्लेट को बाकी लॉजिक के साथ डिस्ट्रिब्यूट करना मुश्किल हो जाता है. इसके अलावा, टेंप्लेट एलिमेंट का इस्तेमाल करने का तरीका, ज़रूरी कोड के लिए स्वाभाविक होता है. कई मामलों में, इससे डिक्लेरेटिव कोडिंग पैटर्न की तुलना में कम पढ़ा जाने वाला कोड बनता है.

ऐसे में, lit-html काम आता है! Lit html, Lit का रेंडरिंग सिस्टम है. इसकी मदद से, JavaScript में एचटीएमएल टेंप्लेट लिखे जा सकते हैं. इसके बाद, उन टेंप्लेट को डेटा के साथ रेंडर और फिर से रेंडर किया जा सकता है, ताकि DOM बनाया और अपडेट किया जा सके. यह लोकप्रिय JSX और VDOM लाइब्रेरी की तरह ही है. हालांकि, यह ब्राउज़र में नेटिव तौर पर काम करता है और कई मामलों में ज़्यादा बेहतर तरीके से काम करता है.

Lit HTML का इस्तेमाल करना

इसके बाद, नेटिव वेब कॉम्पोनेंट rating-element को माइग्रेट करके Lit टेंप्लेट का इस्तेमाल करें. Lit टेंप्लेट, टैग किए गए टेंप्लेट लिटरल का इस्तेमाल करते हैं. ये ऐसे फ़ंक्शन होते हैं जो टेंप्लेट स्ट्रिंग को खास सिंटैक्स के साथ आर्ग्युमेंट के तौर पर लेते हैं. इसके बाद, Lit तेज़ी से रेंडर करने के लिए, बैकग्राउंड में टेंप्लेट एलिमेंट का इस्तेमाल करता है. साथ ही, सुरक्षा के लिए कुछ सैनिटाइज़ेशन सुविधाएँ भी उपलब्ध कराता है. index.html में मौजूद <template> को Lit टेंप्लेट में माइग्रेट करें. इसके लिए, वेब कॉम्पोनेंट में render() तरीका जोड़ें:

index.js

// Dont forget to import from Lit!
import {render, html} from 'lit';

class RatingElement extends HTMLElement {
  ...
  render() {
    if (!this.shadowRoot) {
      return;
    }

    const template = html`
      <style>
        :host {
          display: inline-flex;
          align-items: center;
        }
        button {
          background: transparent;
          border: none;
          cursor: pointer;
        }

       :host([vote=up]) .thumb_up {
         fill: green;
       }

       :host([vote=down]) .thumb_down {
         fill: red;
       }
      </style>
      <button class="thumb_down">
        <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
      </button>
      <span class="rating">${this.rating}</span>
      <button class="thumb_up">
        <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
      </button>`;

    render(template, this.shadowRoot);
  }
}

आपके पास index.html से भी अपना टेंप्लेट मिटाने का विकल्प होता है. इस रेंडरिंग के तरीके में, template नाम का एक वैरिएबल तय किया जाता है. साथ ही, html टैग किए गए टेंप्लेट लिटरल फ़ंक्शन को शुरू किया जाता है. आपको यह भी दिखेगा कि आपने span.rating एलिमेंट में, ${...} के टेंप्लेट लिटरल इंटरपोलेशन सिंटैक्स का इस्तेमाल करके, सामान्य डेटा बाइंडिंग की है. इसका मतलब है कि आपको उस नोड को अपडेट करने की ज़रूरत नहीं होगी. इसके अलावा, lit render तरीके को कॉल किया जाता है. यह टेंप्लेट को शैडो रूट में सिंक्रोनस तरीके से रेंडर करता है.

डिक्लेरेटिव सिंटैक्स पर माइग्रेट करना

अब जब आपने <template> एलिमेंट को हटा दिया है, तो कोड को फिर से फ़ैक्टर करें, ताकि वह नई तय की गई render विधि को कॉल कर सके. लिसनर कोड को हटाने के लिए, lit के इवेंट लिसनर बाइंडिंग का इस्तेमाल किया जा सकता है:

index.js

<button
    class="thumb_down"
    @click=${() => {this.vote = 'down'}}>
...
<button
    class="thumb_up"
    @click=${() => {this.vote = 'up'}}>

Lit टेंप्लेट, @EVENT_NAME बाइंडिंग सिंटैक्स का इस्तेमाल करके, किसी नोड में इवेंट लिसनर जोड़ सकते हैं. इस मामले में, इन बटन पर क्लिक किए जाने पर, vote प्रॉपर्टी अपडेट की जाती है.

इसके बाद, constructor और connectedCallback और disconnectedCallback में इवेंट लिसनर के इनिशियलाइज़ेशन कोड को हटाएं:

index.js

constructor() {
  super();
  this._rating = 0;
  this._vote = null;
}

connectedCallback() {
  this.attachShadow({mode: 'open'});
  this.render();
}

// remove disonnectedCallback and _onUpClick and _onDownClick

आपने तीनों कॉलबैक से क्लिक लिसनर लॉजिक हटा दिया है. साथ ही, disconnectedCallback को भी पूरी तरह से हटा दिया है! आपने connectedCallback से सभी डीओएम इनिशियलाइज़ेशन कोड भी हटा दिए हैं. इससे यह ज़्यादा बेहतर दिख रहा है. इसका यह भी मतलब है कि अब आपको _onUpClick और _onDownClick लिसनर के तरीकों का इस्तेमाल नहीं करना पड़ेगा!

आखिर में, प्रॉपर्टी सेटर को render तरीके का इस्तेमाल करने के लिए अपडेट करें, ताकि प्रॉपर्टी या एट्रिब्यूट में बदलाव होने पर, DOM अपडेट हो सके:

index.js

set rating(value) {
  this._rating = value;
  this.render();
}

...

set vote(newValue) {
  const oldValue = this._vote;
  if (newValue === oldValue) {
    return;
  }

  if (newValue === 'up') {
    if (oldValue === 'down') {
      this.rating += 2;
    } else {
      this.rating += 1;
    }
  } else if (newValue === 'down') {
    if (oldValue === 'up') {
      this.rating -= 2;
    } else {
      this.rating -= 1;
    }
  }

  this._vote = newValue;
  this.setAttribute('vote', newValue);
  // add render method
  this.render();
}

यहां, आपने rating सेटर से DOM अपडेट करने का लॉजिक हटा दिया है. साथ ही, vote सेटर से render को कॉल करने की सुविधा जोड़ दी है. अब टेंप्लेट को आसानी से पढ़ा जा सकता है, क्योंकि अब यह देखा जा सकता है कि बाइंडिंग और इवेंट लिसनर कहां लागू किए गए हैं.

पेज को रीफ़्रेश करें. इसके बाद, आपको रेटिंग देने वाला बटन दिखेगा. अपवोट करने पर, यह बटन ऐसा दिखेगा!

थंब्स अप और थंब्स डाउन रेटिंग वाला स्लाइडर. इसकी वैल्यू 6 है और थंब्स अप को हरे रंग से दिखाया गया है

8. LitElement

LitElement का इस्तेमाल क्यों करें

कोड में अब भी कुछ समस्याएं मौजूद हैं. अगर vote प्रॉपर्टी या एट्रिब्यूट में बदलाव किया जाता है, तो इससे rating प्रॉपर्टी में बदलाव हो सकता है. इस वजह से, render को दो बार कॉल किया जाएगा. बार-बार रेंडर करने के अनुरोधों के बावजूद, JavaScript VM अब भी उस फ़ंक्शन को दो बार सिंक्रोनस तरीके से कॉल करने में समय ले रहा है. दूसरा, नई प्रॉपर्टी और एट्रिब्यूट जोड़ने में काफ़ी समय लगता है, क्योंकि इसके लिए बहुत ज़्यादा बॉयलरप्लेट कोड की ज़रूरत होती है. ऐसे में, LitElement आपकी मदद कर सकता है!

LitElement, Lit की बेस क्लास है. इसकी मदद से, तेज़ और हल्के वेब कॉम्पोनेंट बनाए जा सकते हैं. इनका इस्तेमाल अलग-अलग फ़्रेमवर्क और एनवायरमेंट में किया जा सकता है. इसके बाद, देखें कि LitElement का इस्तेमाल करने के लिए, लागू करने के तरीके में बदलाव करके, rating-element में हमारे लिए क्या किया जा सकता है!

LitElement का इस्तेमाल करना

lit पैकेज से LitElement बेस क्लास को इंपोर्ट करके सबक्लासिंग शुरू करें:

index.js

import {LitElement, html, css} from 'lit';

class RatingElement extends LitElement {
// remove connectedCallback()
...

आपने LitElement इंपोर्ट किया है, जो rating-element के लिए नई बेस क्लास है. इसके बाद, html इंपोर्ट किया जाता है. आखिर में, css इंपोर्ट किया जाता है. इसकी मदद से, हम सीएसएस मैथ, टेंप्लेटिंग, और अन्य सुविधाओं के लिए सीएसएस टैग किए गए टेंप्लेट लिटरल तय कर सकते हैं.

इसके बाद, स्टाइल को रेंडर करने के तरीके से हटाकर, Lit की स्टैटिक स्टाइलशीट में ले जाएं:

index.js

class RatingElement extends LitElement {
  static get styles() {
    return css`
      :host {
        display: inline-flex;
        align-items: center;
      }
      button {
        background: transparent;
        border: none;
        cursor: pointer;
      }

      :host([vote=up]) .thumb_up {
        fill: green;
      }

      :host([vote=down]) .thumb_down {
        fill: red;
      }
    `;
  }
 ...

Lit में ज़्यादातर स्टाइल यहीं मौजूद होती हैं. Lit इन स्टाइल को लेगा और ब्राउज़र की सुविधाओं का इस्तेमाल करेगा. जैसे, कंस्ट्रक्टेबल स्टाइलशीट. इससे रेंडरिंग में कम समय लगेगा. साथ ही, अगर ज़रूरी हो, तो इसे पुराने ब्राउज़र पर Web Components polyfill के ज़रिए पास करेगा.

लाइफ़साइकल

Lit, नेटिव वेब कॉम्पोनेंट कॉलबैक के अलावा, रेंडर लाइफ़साइकल कॉलबैक के तरीके का एक सेट उपलब्ध कराता है. ये कॉलबैक, Lit में तय की गई प्रॉपर्टी में बदलाव होने पर ट्रिगर होते हैं.

इस सुविधा का इस्तेमाल करने के लिए, आपको स्टैटिक तौर पर यह एलान करना होगा कि कौनसी प्रॉपर्टी, रेंडर लाइफ़साइकल को ट्रिगर करेंगी.

index.js

static get properties() {
  return {
    rating: {
      type: Number,
    },
    vote: {
      type: String,
      reflect: true,
    }
  };
}

// remove observedAttributes() and attributeChangedCallback()
// remove set rating() get rating()

यहां यह तय किया जाता है कि rating और vote, LitElement रेंडरिंग लाइफ़साइकल को ट्रिगर करेंगे. साथ ही, उन टाइप को भी तय किया जाता है जिनका इस्तेमाल स्ट्रिंग एट्रिब्यूट को प्रॉपर्टी में बदलने के लिए किया जाएगा.

<user-profile .name=${this.user.name} .age=${this.user.age}>
  ${this.user.family.map(member => html`
        <family-member
             .name=${member.name}
             .relation=${member.relation}>
        </family-member>`)}
</user-profile>

इसके अलावा, vote प्रॉपर्टी पर मौजूद reflect फ़्लैग, होस्ट एलिमेंट के vote एट्रिब्यूट को अपने-आप अपडेट कर देगा. इस एट्रिब्यूट को आपने vote सेटर में मैन्युअल तरीके से ट्रिगर किया था.

अब आपके पास स्टैटिक प्रॉपर्टी ब्लॉक है. इसलिए, एट्रिब्यूट और प्रॉपर्टी रेंडर अपडेट करने के लॉजिक को हटाया जा सकता है. इसका मतलब है कि इन तरीकों को हटाया जा सकता है:

  • connectedCallback
  • observedAttributes
  • attributeChangedCallback
  • rating (सेटर और गैटर)
  • vote (सेटर और गेटर, लेकिन सेटर से बदलाव का लॉजिक बनाए रखें)

आपको constructor को बनाए रखना होगा. साथ ही, willUpdate के लाइफ़साइकल का नया तरीका जोड़ना होगा:

index.js

constructor() {
  super();
  this.rating = 0;
  this.vote = null;
}

willUpdate(changedProps) {
  if (changedProps.has('vote')) {
    const newValue = this.vote;
    const oldValue = changedProps.get('vote');

    if (newValue === 'up') {
      if (oldValue === 'down') {
        this.rating += 2;
      } else {
        this.rating += 1;
      }
    } else if (newValue === 'down') {
      if (oldValue === 'up') {
        this.rating -= 2;
      } else {
        this.rating -= 1;
      }
    }
  }
}

// remove set vote() and get vote()

यहां, आपको सिर्फ़ rating और vote को शुरू करना है. साथ ही, vote सेटर लॉजिक को willUpdate लाइफ़साइकल के तरीके में ले जाना है. किसी अपडेट की जा रही प्रॉपर्टी में बदलाव होने पर, willUpdate को render से पहले कॉल किया जाता है. ऐसा इसलिए, क्योंकि LitElement प्रॉपर्टी में हुए बदलावों को बैच करता है और रेंडरिंग को एसिंक्रोनस बनाता है. willUpdate में this.rating जैसी रिएक्टिव प्रॉपर्टी में बदलाव करने पर, गैर-ज़रूरी render लाइफ़साइकल कॉल ट्रिगर नहीं होंगे.

आखिर में, render एक LitElement लाइफ़साइकल मेथड है. इसके लिए, हमें एक Lit टेंप्लेट वापस करना होता है:

index.js

render() {
  return html`
    <button
        class="thumb_down"
        @click=${() => {this.vote = 'down'}}>
      <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
    </button>
    <span class="rating">${this.rating}</span>
    <button
        class="thumb_up"
        @click=${() => {this.vote = 'up'}}>
      <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
    </button>`;
}

अब आपको शैडो रूट की जांच करने की ज़रूरत नहीं है. साथ ही, आपको 'lit' पैकेज से पहले इंपोर्ट किए गए render फ़ंक्शन को कॉल करने की भी ज़रूरत नहीं है.

अब आपको झलक में अपना एलिमेंट दिखना चाहिए. इस पर क्लिक करें!

9. बधाई हो

बधाई हो, आपने स्क्रैच से एक वेब कॉम्पोनेंट बना लिया है और उसे LitElement में बदल दिया है!

Lit बहुत छोटा है (< 5kb minified + gzipped), बहुत तेज़ है, और इसके साथ कोडिंग करना बहुत मज़ेदार है! इससे ऐसे कॉम्पोनेंट बनाए जा सकते हैं जिनका इस्तेमाल अन्य फ़्रेमवर्क में किया जा सकता है. इसके अलावा, इससे पूरे ऐप्लिकेशन भी बनाए जा सकते हैं!

अब आपको पता है कि वेब कॉम्पोनेंट क्या होता है, इसे कैसे बनाया जाता है, और Lit इसे बनाने की प्रोसेस को कितना आसान बना देता है!

कोड चेकपॉइंट

क्या आपको हमारे कोड से अपने कोड की तुलना करनी है? इसकी तुलना यहां करें.

आगे क्या करना है?

कुछ अन्य कोडलैब आज़माएं!

इस बारे में और पढ़ें

कम्यूनिटी

  • Lit and Friends Slack - यह वेब कॉम्पोनेंट का सबसे बड़ा समुदाय है
  • Twitter पर@buildWithLit - यह Lit बनाने वाली टीम का Twitter खाता है
  • Web Components SF - सैन फ़्रांसिस्को के लिए Web Components meetup