1. مقدمة
تاريخ آخر تعديل: 10-08-2021
Web Components
مكوّنات الويب هي مجموعة من واجهات برمجة التطبيقات لمنصة الويب تتيح لك إنشاء علامات HTML جديدة مخصّصة وقابلة لإعادة الاستخدام ومغلّفة لاستخدامها في صفحات الويب وتطبيقات الويب. ستعمل المكوّنات والأدوات المخصّصة المستندة إلى معايير Web Component على جميع المتصفّحات الحديثة، ويمكن استخدامها مع أي مكتبة أو إطار عمل JavaScript يعمل مع HTML.
ما هو Lit؟
Lit هي مكتبة بسيطة لإنشاء مكونات ويب سريعة وخفيفة الوزن تعمل في أي إطار عمل أو بدون أي إطار عمل على الإطلاق. باستخدام Lit، يمكنك إنشاء تطبيقات ومكوّنات وأنظمة تصميم قابلة للمشاركة وغير ذلك.
توفّر Lit واجهات برمجة تطبيقات لتسهيل مهام "مكوّنات الويب" الشائعة، مثل إدارة الخصائص والسمات والعرض.
ما ستتعلمه
- ما هو مكوّن الويب؟
- مفاهيم "مكوّنات الويب"
- كيفية إنشاء مكوّن ويب
- ما هما lit-html وLitElement؟
- ما يفعله Lit بالإضافة إلى مكوّن الويب
ما ستنشئه
- مكوّن ويب بسيط للإعجاب أو عدم الإعجاب
- مكوّن ويب مستند إلى Lit مع زرَّي إعجاب وعدم إعجاب
المتطلبات
- أي متصفّح حديث تم تحديثه (Chrome أو Safari أو Firefox أو Chromium Edge) تعمل "مكوّنات الويب" في جميع المتصفّحات الحديثة، وتتوفّر عمليات التعبئة المتعدّدة لمتصفّح Microsoft Internet Explorer 11 وMicrosoft Edge غير المستند إلى Chromium.
- معرفة بلغات HTML وCSS وJavaScript وأدوات مطوّري البرامج في Chrome
2. إعداد "الملعب" واستكشافه
الوصول إلى الرمز
خلال تجربة الترميز، ستظهر روابط إلى ساحة تجارب Lit على النحو التالي:
ساحة اللعب هي بيئة اختبار للرموز البرمجية تعمل بالكامل في المتصفّح. يمكنه تجميع ملفات TypeScript وJavaScript وتشغيلها، ويمكنه أيضًا حل عمليات الاستيراد تلقائيًا إلى وحدات node، على سبيل المثال:
// before
import './my-file.js';
import 'lit';
// after
import './my-file.js';
import 'https://unpkg.com/lit?module';
يمكنك إكمال البرنامج التعليمي بالكامل في ساحة تجارب Lit، باستخدام نقاط التحقّق هذه كنقاط بداية. إذا كنت تستخدم VS Code، يمكنك استخدام نقاط التحقّق هذه لتنزيل رمز البداية لأي خطوة، بالإضافة إلى استخدامها للتحقّق من عملك.
استكشاف واجهة مستخدم "ساحة الألعاب المضيئة"

تُبرز لقطة شاشة لواجهة مستخدم Lit playground الأقسام التي ستستخدمها في هذا الدرس البرمجي.
- أداة اختيار الملفات لاحظ زرّ الإضافة...
- محرّر الملفات
- معاينة الرمز البرمجي
- زر إعادة التحميل
- زر التنزيل
إعداد VS Code (متقدّم)
في ما يلي مزايا استخدام إعداد VS Code هذا:
- التحقّق من نوع النموذج
- ميزة IntelliSense والإكمال التلقائي للنماذج
إذا كان لديك NPM وVS Code (مع المكوّن الإضافي lit-plugin) مثبّتَين مسبقًا وتعرف كيفية استخدام هذه البيئة، يمكنك ببساطة تنزيل هذه المشاريع وبدء استخدامها من خلال اتّباع الخطوات التالية:
- انقر على زر التنزيل
- استخراج محتوى ملف tar إلى دليل
- ثبِّت خادم تطوير يمكنه تحليل محدّدات وحدات البرامج المجردة (ينصح فريق Lit باستخدام @web/dev-server)
- في ما يلي مثال
package.json
- في ما يلي مثال
- شغِّل خادم التطوير وافتح المتصفّح (إذا كنت تستخدم
@web/dev-server، يمكنك استخدامnpx web-dev-server --node-resolve --watch --open)- إذا كنت تستخدم المثال
package.jsonاستخدِمnpm run serve
- إذا كنت تستخدم المثال
3- تحديد عنصر مخصّص
العناصر المخصّصة
مكوّنات الويب هي مجموعة من 4 واجهات برمجة تطبيقات ويب أصلية. وهي:
- وحدات ES
- العناصر المخصّصة
- 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 بالاسم tagName ‘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 {}
دورة حياة العناصر المخصّصة
تتضمّن العناصر المخصّصة مجموعة من خطافات دورة الحياة. وهي:
constructorconnectedCallbackdisconnectedCallbackattributeChangedCallbackadoptedCallback
يتم استدعاء constructor عند إنشاء العنصر لأول مرة، مثلاً من خلال استدعاء document.createElement(‘rating-element') أو new RatingElement(). يُعدّ الدالة الإنشائية مكانًا جيدًا لإعداد العنصر، ولكن يُعدّ من الممارسات السيئة عادةً إجراء عمليات تعديل DOM في الدالة الإنشائية لأسباب تتعلّق بأداء "بدء التشغيل" للعنصر.
يتم استدعاء connectedCallback عندما يتم ربط العنصر المخصّص بـ DOM. وهذا هو المكان الذي تحدث فيه عادةً عمليات معالجة DOM الأولية.
يتم استدعاء disconnectedCallback بعد إزالة العنصر المخصّص من DOM.
يتم استدعاء attributeChangedCallback(attrName, oldValue, newValue) عند تغيير أيّ من السمات التي يحدّدها المستخدم.
يتم استدعاء adoptedCallback عندما يتم اعتماد العنصر المخصّص من documentFragment آخر في المستند الرئيسي من خلال adoptNode، مثل HTMLTemplateElement.
عرض 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.
إرفاق Shadow Root
أضِف Shadow Root إلى العنصر واعرض 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 أنّه يمكن فحص محتوى shadow DOM، كما يتيح الوصول إلى جذر shadow DOM من خلال this.shadowRoot أيضًا. ألقِ نظرة على "مكوّن الويب" في "أداة فحص Chrome" أيضًا:

من المفترض أن يظهر لك الآن عنصر جذر ظل قابل للتوسيع يتضمّن المحتوى. ويُطلق على كل ما يقع داخل جذر shadow اسم Shadow DOM. إذا اخترت عنصر التقييم في "أدوات مطوّري Chrome" واستدعيت $0.children، ستلاحظ أنّه لا يعرض أي عناصر فرعية. ويرجع ذلك إلى أنّ Shadow DOM لا يُعتبر جزءًا من شجرة نموذج العناصر في المستند نفسها مثل العناصر الفرعية المباشرة، بل يُعتبر جزءًا من شجرة Shadow.
Light DOM
تجربة: إضافة عقدة كعنصر فرعي مباشر من <rating-element>:
index.html
<rating-element>
<div>
This is the light DOM!
</div>
</rating-element>
أعِد تحميل الصفحة، وسترى أنّ عقدة DOM الجديدة هذه في Light DOM لهذا العنصر المخصّص لا تظهر على الصفحة. ويرجع ذلك إلى أنّ Shadow DOM يتضمّن ميزات للتحكّم في كيفية عرض عُقد Light DOM في Shadow DOM من خلال عناصر <slot>.
5- نماذج HTML
مزايا استخدام ميزة "النماذج"
قد يؤدي استخدام innerHTML وسلاسل حرفية للنماذج بدون تعقيم إلى حدوث مشاكل أمان بسبب إدخال نصوص برمجية. تضمّنت الطرق السابقة استخدام DocumentFragment، ولكنّها تتضمّن أيضًا مشاكل أخرى، مثل تحميل الصور وتشغيل النصوص البرمجية عند تحديد النماذج، بالإضافة إلى وضع عقبات أمام إمكانية إعادة الاستخدام. هنا يأتي دور العنصر <template>، إذ توفّر القوالب نموذج 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 عملية استنساخ عميق. بعد ذلك، يمكنك تهيئة نموذج العناصر في المستند باستخدام البيانات.
تهانينا، لديك الآن "مكوّن ويب". لا يفعل هذا الرمز أي شيء بعد، لذا عليك إضافة بعض الوظائف إليه.
6. إضافة وظائف
عمليات الربط بين المواقع
في الوقت الحالي، الطريقة الوحيدة لضبط التقييم في عنصر التقييم هي إنشاء العنصر، وضبط السمة 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 لسمة التقييم، ثم تعديل نص عنصر التقييم إذا كان متاحًا. هذا يعني أنّه في حال ضبطت خاصية التقييم على العنصر، سيتم تعديل العرض. يمكنك إجراء اختبار سريع في وحدة تحكّم "أدوات المطوّرين".
عمليات ربط السمات
الآن، عدِّل طريقة العرض عند تغيير السمة، وهذا يشبه تعديل طريقة عرض حقل الإدخال عند ضبط <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>
في Shadow DOM، يشير أداة الاختيار :host إلى العقدة أو العنصر المخصّص الذي تم ربط Shadow Root به. في هذه الحالة، إذا كانت قيمة السمة 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;
}
يمكنك إعداد السمة _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 بشكلٍ مناسب.
تهانينا، لديك الآن Web Component كامل الميزات. جرِّب النقر على بعض الأزرار. المشكلة الآن هي أنّ ملف 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 Native Web Component لاستخدام نموذج Lit الذي يستخدم Tagged Template Literals، وهي دوال تأخذ سلاسل النموذج كمعلمات باستخدام بنية خاصة. تستخدم 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 في Lit التي تعرض النموذج بشكل متزامن في جذر الظل.
الانتقال إلى البنية التعريفية
بعد إزالة العنصر <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 بالكامل. تمكّنت أيضًا من إزالة جميع رموز تهيئة DOM من connectedCallback، ما جعلها تبدو أكثر أناقة. يعني هذا أيضًا أنّه يمكنك التخلّص من طريقتَي معالجة الأحداث _onUpClick و_onDownClick.
أخيرًا، عدِّل أدوات ضبط المواقع الإلكترونية لاستخدام الطريقة render حتى يتمكّن نموذج المستند من التعديل عند تغيير المواقع الإلكترونية أو السمات:
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();
}
في هذا المثال، تمكّنت من إزالة منطق تعديل نموذج المستند (DOM) من الدالة الإعدادية rating وأضفت استدعاءً للدالة render من الدالة الإعدادية vote. أصبح القالب الآن أكثر قابلية للقراءة، إذ يمكنك الآن معرفة الأماكن التي يتم فيها تطبيق عمليات الربط وأدوات معالجة الأحداث.
أعِد تحميل الصفحة، ومن المفترض أن يظهر زر تقييم يعمل بشكل صحيح، ويجب أن يبدو على النحو التالي عند الضغط على زر الإعجاب!

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 من أجل عمليات الرياضيات والقوالب وغيرها من الميزات في الخلفية.
بعد ذلك، انقل الأنماط من طريقة العرض إلى ورقة الأنماط الثابتة في 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>
بالإضافة إلى ذلك، سيعدّل الخيار reflect في السمة vote تلقائيًا السمة vote للعنصر المضيف الذي تم تفعيله يدويًا في أداة ضبط vote.
بعد الحصول على حزمة الخصائص الثابتة، يمكنك إزالة جميع منطق تعديل عرض السمات والخصائص. وهذا يعني أنّه يمكنك إزالة الطرق التالية:
connectedCallbackobservedAttributesattributeChangedCallbackrating(تحديد القيم والحصول عليها)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 كيلوبايت بعد التصغير والضغط)، وسريع جدًا، وممتع جدًا في كتابة الرموز البرمجية. يمكنك إنشاء مكوّنات تستخدمها أُطر أخرى، أو يمكنك إنشاء تطبيقات متكاملة باستخدامها.
أصبحت الآن تعرف ما هي "مكوّنات الويب" وكيفية إنشائها وكيف تسهّل Lit عملية إنشائها.
نقطة التحقّق من الرمز
هل تريد مقارنة الرمز النهائي بالرمز الذي أرسلناه؟ يمكنك مقارنته هنا.
ما هي الخطوات التالية؟
اطّلِع على بعض الدروس التطبيقية الأخرى حول الترميز.
Further reading
- برنامج تعليمي تفاعلي حول Lit
- The Lit Docs
- Open Web Components: منتدى تديره مجموعة من المستخدمين ويقدّم إرشادات وأدوات
- WebComponents.dev: إنشاء Web Component في جميع أُطر العمل المعروفة
المجتمع
- منتدى Lit and Friends على Slack: أكبر منتدى لمكوّنات الويب
- @buildWithLit على Twitter: حساب فريق Lit على Twitter
- Web Components SF: اجتماع حول Web Components في سان فرانسيسكو