من مكوّن الويب إلى عنصر Lit

1. مقدمة

تاريخ آخر تعديل: 10/08/2021

مكوّنات الويب

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

ما هو Lit

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

يوفر Lit واجهات برمجة تطبيقات لتبسيط مهام مكونات الويب الشائعة مثل إدارة الخصائص والسمات والعرض.

المعلومات التي ستطّلع عليها

  • ما هو مكوّن الويب؟
  • مفاهيم مكونات الويب
  • كيفية إنشاء مكون ويب
  • تعريف lit-html وLitElement
  • الإجراءات التي تتخذها أداة Lit فوق مكوَّن الويب

ما الذي ستنشئه

  • مكوّن الويب فانيلا يعبّر عن إعجابه أو عدم إعجابه
  • مكوّن ويب مستند إلى الإضاءة لا يعجبني ولا يعجبني

المتطلبات

  • أي متصفح حديث ومحدَّث (Chrome وSafari وFirefox وChromium Edge). تعمل مكوّنات الويب في جميع المتصفّحات الحديثة، وتتوفر رموز polyfill في Microsoft Internet Explorer 11 وMicrosoft Edge الذي لا يعمل مع Chromium.
  • معرفة HTML وCSS وJavaScript وأدوات مطوري البرامج في Chrome

2. بدء الإعداد استكشاف Playground

الوصول إلى الرمز

خلال الدرس التطبيقي حول الترميز، ستكون هناك روابط إلى ملعب Lit على النحو التالي:

الملعب هو وضع حماية للرموز يتم تشغيله بالكامل في متصفحك. ويمكنه تجميع ملفات TypeScript وJavaScript وتشغيلها، كما يمكنه أيضًا تحليل عمليات الاستيراد تلقائيًا إلى وحدات العقدة. مثلاً:

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

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

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

استكشِف واجهة المستخدم في ساحة مضيئة

تتم تسمية شريط علامة تبويب ملف تحديد الملف باسم القسم 1، وقسم تعديل التعليمات البرمجية باعتباره القسم 2، ومعاينة الناتج بالقسم 3، وزر إعادة تحميل المعاينة باعتباره القسم 4

تُبرز لقطة شاشة واجهة المستخدم في "المنصة" في "المنصة" الأقسام التي ستستخدمها في هذا الدرس التطبيقي حول الترميز.

  1. أداة اختيار الملفات لاحظ زر الإضافة...
  2. محرر الملفات.
  3. معاينة الرمز
  4. زر "إعادة التحميل"
  5. زر التنزيل

إعداد رمز VS (متقدّم)

فيما يلي فوائد استخدام إعداد VS Code هذا:

  • التحقّق من نوع النموذج
  • ذكاء النماذج الإكمال التلقائي

إذا سبق لك تثبيت NPM وVS Code (مع المكوّن الإضافي lit- Plugin) وكنت تعرف كيفية استخدام تلك البيئة، يمكنك ببساطة تنزيل هذه المشاريع وبدء تشغيلها عن طريق اتّباع الخطوات التالية:

  • اضغط على زر التنزيل.
  • استخراج محتوى ملف tar إلى دليل
  • تثبيت خادم مطوّر يمكنه حل محددات الوحدات المجرّدة (يوصي فريق Lit بـ @web/dev-server)
  • تشغيل خادم مطوّر البرامج وفتح المتصفح (إذا كنت تستخدم @web/dev-server، يمكنك استخدام npx web-dev-server --node-resolve --watch --open)
    • إذا كنت تستخدم المثال package.json، استخدِم npm run serve

3- تعريف عنصر مخصص

العناصر المخصصة

مكوّنات الويب هي مجموعة من 4 واجهات برمجة تطبيقات أصلية للويب. وهي:

  • وحدات اللغة الإسبانية
  • العناصر المخصصة
  • نموذج Shadow DOM
  • نماذج HTML

لقد سبق لك استخدام مواصفات وحدات ES، التي تسمح لك بإنشاء وحدات JavaScript مع عمليات الاستيراد والتصدير التي يتم تحميلها في الصفحة باستخدام <script type="module">.

تعريف عنصر مخصَّص

تتيح مواصفات العناصر المخصصة للمستخدمين تحديد عناصر HTML الخاصة بهم باستخدام JavaScript. يجب أن تحتوي الأسماء على واصلة (-) لتمييزها عن عناصر المتصفّح الأصلي. محو ملف index.js وتحديد فئة عنصر مخصّص:

index.js

class RatingElement extends HTMLElement {}

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

يتم تحديد العنصر المخصّص من خلال ربط فئة تمتد إلى HTMLElement باسم علامة شرطة. تطلب المكالمة إلى customElements.define من المتصفّح ربط الفئة RatingElement باسم العلامة ‘rating-element'. ويعني هذا أنّ كل عنصر في المستند يحمل الاسم <rating-element> سيتم ربطه بهذه الفئة.

يُرجى وضع <rating-element> في نص المستند ورؤية ما يتم عرضه.

index.html

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

الآن، بالنظر إلى الناتج، سترى أنه لم يتم عرض أي شيء. هذا الأمر متوقّع، لأنّك لم يسبق لك إعلام المتصفّح بكيفية عرض <rating-element>. يمكنك التأكّد من نجاح تعريف العنصر المخصّص من خلال اختيار <rating-element> في "أدوات مطوري البرامج في Chrome". محدد العنصر، وفي وحدة التحكم، يستدعي:

$0.constructor

ما الذي يجب أن ينتج عنه:

class RatingElement extends HTMLElement {}

مراحل نشاط العنصر المخصّص

تشمل العناصر المخصّصة مجموعة من المعلقات الخاصة بمراحل النشاط. وهي:

  • constructor
  • connectedCallback
  • disconnectedCallback
  • attributeChangedCallback
  • adoptedCallback

يتم استدعاء constructor عند إنشاء العنصر لأوّل مرة، مثلاً من خلال استدعاء document.createElement(‘rating-element') أو new RatingElement(). تعتبر الدالة الإنشائية مكانًا جيدًا لإعداد العنصر، ولكن عادة ما يُعد إجراء عمليات معالجة DOM في الدالة الإنشائية للعنصر "boot-up" ممارسة سيئة. لأسباب تتعلق بالأداء.

يتم استدعاء connectedCallback عند إرفاق العنصر المخصّص بنموذج العناصر في المستند. عادةً ما يكون هذا هو المكان الذي تحدث فيه عمليات معالجة DOM الأولية.

يتم استدعاء disconnectedCallback بعد إزالة العنصر المخصّص من DOM.

ويتم طلب attributeChangedCallback(attrName, oldValue, newValue) عند تغيير أي من السمات التي يحدّدها المستخدم.

يتم استدعاء adoptedCallback عند استخدام العنصر المخصّص من documentFragment آخر في المستند الرئيسي عبر adoptNode كما في HTMLTemplateElement.

Render DOM

الآن، ارجع إلى العنصر المخصص واربطه ببعض عناصر 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، يمكنك إضافة عناصر DOM الثانوية إلى <rating-element> لعرض التقييم الحالي مع زرَّي الإعجاب وعدم الإعجاب.

4. نموذج Shadow DOM

لماذا Shadow DOM؟

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

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 قد يؤدي إلى مشكلات أكبر للتطبيقات الأكثر تعقيدًا. وهنا يأتي دور Shadow 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);

عند إعادة تحميل الصفحة، ستلاحظ أن الأنماط في المستند الرئيسي لم تعد يمكنها تحديد العُقد داخل Shadow Root.

كيف فعلت ذلك؟ في connectedCallback، سميت this.attachShadow، والذي يربط جذر الظل بعنصر. ويعني الوضع open أنّ محتوى الظل قابل للفحص ويتيح الوصول إلى جذر الظل عبر this.shadowRoot أيضًا. ألق نظرة على مكوِّن الويب في عارض Chrome أيضًا:

شجرة دوم في أداة فحص Chrome. هناك <rating-element> مع#shadow-root (open) كعنصر تابع له، وDOM من قبل هذا shadowroot.

من المفترض أن يظهر لك الآن جذر تظليل قابل للتوسيع يضم المحتويات. كل شيء داخل جذر الظل يسمى Shadow DOM. إذا اخترت العنصر "rating" (التقييم) في "أدوات مطوري البرامج في Chrome" واتصلت بـ $0.children، ستلاحظ أنّه لا يتم عرض أي عناصر فرعية. ويرجع ذلك إلى أنّ Shadow DOM لا يعتبر جزءًا من شجرة DOM نفسها كعناصر ثانوية مباشرة، بل شجرة الظل.

Light DOM

تجربة: إضافة عقدة كعنصر ثانوي مباشر للـ <rating-element>:

index.html

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

أعِد تحميل الصفحة، وستلاحظ عدم ظهور عقدة DOM الجديدة هذه في Light DOM الخاص بهذا العنصر المخصّص. والسبب في ذلك هو أنّ Shadow DOM يتضمّن ميزات للتحكّم في كيفية عرض عُقد Light DOM في نطاق الظل من خلال عناصر <slot>.

5- نماذج HTML

مزايا النماذج

قد يؤدي استخدام innerHTML والسلاسل الحرفية للنموذج بدون تعقيم إلى حدوث مشاكل أمنية في إدخال النصوص البرمجية. كانت هناك طرق في الماضي تتضمّن استخدام نماذج DocumentFragment، ولكنّها كانت تحمل أيضًا مشاكل أخرى مثل تحميل الصور والنصوص البرمجية التي يتم تشغيلها عند تحديد النماذج، بالإضافة إلى وضع عقبات بشأن إمكانية إعادة الاستخدام. وهنا يأتي دور عنصر <template>، توفر النماذج inert DOM، وهي طريقة عالية الأداء لاستنباط العُقد، وإنشاء نماذج قابلة لإعادة الاستخدام.

استخدام "النماذج"

بعد ذلك، انقل المكوِّن إلى استخدام نماذج HTML:

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>

لقد نقلت محتوى DOM هنا إلى علامة نموذج في DOM للمستند الرئيسي. الآن أعد بناء تعريف العنصر المخصص:

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 حيث تُجري الوسيطة true استنساخًا عميقًا. ثم تقوم بعد ذلك بتهيئة dom بالبيانات.

تهانينا، لديك الآن مكون ويب! للأسف، لا تفعل أي شيء حتى الآن، لذا عليك بعد ذلك إضافة بعض الوظائف.

6- إضافة الوظائف

روابط المواقع

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

يمكنك جعل العنصر المخصّص يعدِّل العرض عند تغيير السمة 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;
}

عليك إضافة دالة setter وgetter لخاصية rating، ثم تحدّث نص عنصر التقييم إذا كان متوفرًا. وهذا يعني أنه إذا كنت ستُعين خاصية rating على العنصر، فسيتم تحديث طريقة العرض؛ عليك اختباره بسرعة في وحدة التحكّم في "أدوات مطوّري البرامج".

ربط السمات

والآن، حدِّث طريقة العرض عند تغيير السمة؛ يكون هذا مشابهًا لإدخال يعدّل طريقة عرضه عند ضبط <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، يجب ضبط قيمة get ثابتة لـ 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>

في Shadow DOM، تشير أداة اختيار :host إلى العقدة أو العنصر المخصّص المرتبط بجذر الظل. في هذه الحالة، إذا كانت قيمة السمة vote هي "up"، سيتحوّل زر إبداء الإعجاب إلى اللون الأخضر ولكن إذا كانت قيمة السمة vote هي "down", then it will turn the thumb-down button red. والآن، عليك تنفيذ المنطق لذلك من خلال إنشاء سمة أو سمة تعكس vote بالطريقة نفسها التي تتّبعها لتنفيذ rating. ابدأ بدالة setter وgetter:

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

عليك إعداد خاصية المثيل _vote باستخدام null في constructor، وفي أداة ضبط الإعدادات، تتحقق مما إذا كانت القيمة الجديدة مختلفة. إذا كان الأمر كذلك، عليك تعديل التقييم وفقًا لذلك، والأهم من ذلك، عليك إعادة تقديم سمة 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. يمكنك إضافة vote إلى observedAttributes وإعداد السمة vote في attributeChangedCallback. وأخيرًا، أضف بعض أدوات معالجة أحداث النقر لمنح الأزرار وظيفة!

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 سطرًا، ويصل ملف HTML إلى 43 سطرًا، ورمز مطوّل وضروري لهذا العنصر البسيط. وهنا يأتي دور مشروع Lit من Google!

7. Lit-html

نقطة التحقّق من الرمز

مزايا استخدام lit-html

في البداية، تُعتبر العلامة <template> مفيدة وعملية، لكنّها غير مضمَّنة في منطق المكوِّن، ما يجعل من الصعب توزيع النموذج مع باقي المنطق. بالإضافة إلى ذلك، تؤدي الطريقة التي يتم بها استخدام عناصر النموذج بطبيعتها إلى التعليمة البرمجية الضرورية، والتي تؤدي في كثير من الحالات إلى تعليمات برمجية أقل قابلية للقراءة مقارنة بأنماط الترميز التعريفي.

وهنا يأتي دور lit-html. Lit html هو نظام العرض من Lit الذي يسمح لك بكتابة نماذج HTML بلغة JavaScript، ثم عرض تلك النماذج وإعادة عرضها مع البيانات بشكل فعّال لإنشاء وتحديث DOM. وهو مشابه لمكتبات JSX وVDOM الشائعة، ولكنه يعمل في الأصل في المتصفح وبكفاءة أكبر في كثير من الحالات.

استخدام Lit HTML

بعد ذلك، يمكنك نقل مكوّن الويب rating-element الأصلي لاستخدام نموذج Lit الذي يستخدم القيم الحرفية للنموذج الذي تم وضع علامة عليه، وهي دوال تأخذ سلاسل النماذج كوسيطات ذات بنية خاصة. بعد ذلك، يستخدم Lit عناصر النموذج بشكل داخلي لتوفير عرض سريع بالإضافة إلى توفير بعض ميزات المعالجة للأمان. ابدأ بنقل <template> في index.html إلى نموذج Lit من خلال إضافة طريقة render() إلى مكوّن webcomponent:

ملف 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 باستخدام بنية الاستيفاء الحرفي للنموذج في ${...}. ويعني ذلك أنك لن تحتاج في النهاية إلى إجراء تحديث إلزامي لتلك العقدة. بالإضافة إلى ذلك، عليك استدعاء طريقة render المضيئة التي تعرض النموذج بشكل متزامن في جذر الظل.

الانتقال إلى بنية البيان

والآن بعد أن تخلصت من العنصر <template>، عليك إعادة ضبط الرمز لاستدعاء طريقة render المحددة حديثًا بدلاً من ذلك. يمكنك البدء بالاستفادة من ربط مستمع الأحداث في lit لإزالة الرمز البرمجي الخاص بالمستمع:

index.js

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

يمكن أن تضيف النماذج التجريبية أداة معالجة حدث إلى عقدة باستخدام بنية ربط @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 بالكامل! تمكنت أيضًا من إزالة كل رموز إعداد DOM من connectedCallback لتبدو أكثر أناقة. وهذا يعني أيضًا أنّه يمكنك التخلّص من طريقتَي المستمعين _onUpClick و_onDownClick.

أخيرًا، يجب تعديل سمة setters من أجل استخدام طريقة 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" وإضافة اتصال إلى "render" من أداة ضبط "vote". أصبح النموذج الآن أكثر قابلية للقراءة حيث يمكنك الآن معرفة مكان تطبيق عمليات الربط وأدوات معالجة الأحداث.

قم بتحديث الصفحة، ويُفترض أن يكون لديك زر تقييم يعمل بهذا الشكل عندما يتم الضغط على التصويت المؤيّد!

شريط تمرير التقييم &quot;أعجبني&quot; و&quot;سيئ&quot; بقيمة 6 والإبهام للأعلى وللأسفل باللون الأخضر

8. LitElement

مزايا استخدام LitElement

لا تزال بعض المشاكل تظهر في الرمز. أولاً، إذا غيّرت سمة أو سمة vote، قد يتم تغيير السمة rating، ما سيؤدي إلى استدعاء render مرّتين. على الرغم من أنّ الطلبات المتكررة للعرض غير قابلة للتنفيذ وفعّالة، لا يزال الجهاز الافتراضي في JavaScript يستغرق وقتًا لاستدعاء هذه الوظيفة مرتين بشكل متزامن. ثانيًا، من الممل إضافة خصائص وسمات جديدة لأنها تتطلب الكثير من الرموز النموذجية. وهنا يأتي دور LitElement!

LitElement هي الفئة الأساسية في Lit لإنشاء مكونات ويب سريعة وخفيفة يمكن استخدامها عبر أطر العمل والبيئات. بعد ذلك، ألقِ نظرة على ما يمكن أن يقدمه لنا LitElement في rating-element من خلال تغيير طريقة التنفيذ ولاستخدامها.

استخدام LitElement

ابدأ باستيراد الفئة الأساسية LitElement وتقسيمها إلى فئات فرعية من حزمة lit:

index.js

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

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

يتم استيراد LitElement، وهي الفئة الأساسية الجديدة لـ rating-element. بعد ذلك، عليك الاحتفاظ باستيراد html وأخيرًا css، ما يسمح لنا بتحديد القيم الحرفية للنموذج الذي يحمل علامة css في الرياضيات والقوالب والميزات الأخرى المتاحة في ملف 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 هذه الأنماط ويستخدم ميزات المتصفح مثل أوراق الأنماط القابلة للإنشاء لتوفير أوقات عرض أسرع وتمريرها من خلال رمز 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>

بالإضافة إلى ذلك، ستعدّل العلامة reflect في السمة vote تلقائيًا سمة 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 يدمج تغييرات الخصائص ويجعل العرض غير متزامن. إنّ التغييرات التي تُجريها على السمات التفاعلية (مثل this.rating) في willUpdate لن تؤدي إلى إجراء مكالمات 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>`;
}

لم تعُد بحاجة إلى البحث عن جذر الظل، ولن تحتاج بعد الآن إلى استدعاء الدالة render التي سبق استيرادها من حزمة 'lit'.

من المفترض أن يظهر العنصر في المعاينة الآن. انقر عليه!

9. تهانينا

تهانينا، لقد نجحت في إنشاء مكون ويب من البداية وطورته إلى LitElement.

يتميز تطبيق Lit بحجم صغير (< 5 كيلوبايت + مضغوط ببرنامج gzip) وسريع للغاية وممتع حقًا للترميز باستخدامه! ويمكنك إنشاء مكونات ليتم استهلاكها من خلال أطر عمل أخرى، أو يمكنك إنشاء تطبيقات كاملة باستخدامها!

أنت تعرف الآن ما هو مكوِّن الويب، وكيف تنشئ واحدًا وكيف يسهل Lit إنشائه!

نقطة التحقّق من الرمز

هل تريد التحقق من الرمز النهائي مقارنةً برمزنا؟ يمكنك المقارنة بينهما هنا.

الخطوات التالية

اطّلِع على بعض الدروس التطبيقية الأخرى حول الترميز.

قراءة إضافية

المجتمع