الكأس المقدسة للمكونات القابلة لإعادة الاستخدام: العناصر المخصصة ، Shadow DOM ، و NPM
نشرت: 2022-03-10بالنسبة لأبسط المكونات ، قد تكون تكلفة العمالة البشرية كبيرة. تقوم فرق UX باختبار قابلية الاستخدام. مجموعة من أصحاب المصلحة يجب أن يوقعوا على التصميم.
يجري المطورون اختبارات AB ، وتدقيقات إمكانية الوصول ، واختبارات الوحدة ، وعمليات التحقق عبر المستعرضات. بمجرد حل المشكلة ، لا تريد تكرار هذا الجهد . من خلال بناء مكتبة مكونات قابلة لإعادة الاستخدام (بدلاً من بناء كل شيء من البداية) ، يمكننا باستمرار الاستفادة من الجهود السابقة وتجنب إعادة النظر في تحديات التصميم والتطوير التي تم حلها بالفعل.

يعد بناء ترسانة من المكونات مفيدًا بشكل خاص لشركات مثل Google التي تمتلك مجموعة كبيرة من مواقع الويب التي تشترك جميعها في علامة تجارية مشتركة. من خلال ترميز واجهة المستخدم الخاصة بهم إلى عناصر واجهة مستخدم قابلة للتكوين ، يمكن للشركات الكبيرة تسريع وقت التطوير وتحقيق الاتساق في التصميم المرئي وتصميم التفاعل مع المستخدم عبر المشاريع. كان هناك زيادة في الاهتمام بأدلة الأنماط ومكتبات الأنماط على مدار السنوات العديدة الماضية. نظرًا لتعدد المطورين والمصممين الموزعين على فرق متعددة ، تسعى الشركات الكبيرة إلى تحقيق الاتساق. يمكننا أن نفعل أفضل من حوامل الألوان البسيطة. ما نحتاجه هو رمز قابل للتوزيع بسهولة .
تقاسم وإعادة استخدام التعليمات البرمجية
يعد نسخ الشفرة ولصقها يدويًا أمرًا سهلاً. ومع ذلك ، فإن الحفاظ على هذا الرمز محدثًا هو كابوس الصيانة. لذلك ، يعتمد العديد من المطورين على مدير الحزم لإعادة استخدام التعليمات البرمجية عبر المشاريع. على الرغم من اسمه ، أصبح Node Package Manager النظام الأساسي الذي لا مثيل له لإدارة الحزم الأمامية . يوجد حاليًا أكثر من 700000 حزمة في سجل NPM ويتم تنزيل مليارات الحزم كل شهر. يمكن تحميل أي مجلد به ملف package.json إلى NPM كحزمة قابلة للمشاركة. بينما يرتبط NPM بشكل أساسي بـ JavaScript ، يمكن أن تتضمن الحزمة CSS والترميز. يجعل NPM من السهل إعادة استخدام ، والأهم من ذلك ، تحديث الكود. بدلاً من الحاجة إلى تعديل الرمز في عدد لا يحصى من الأماكن ، يمكنك تغيير الرمز فقط في الحزمة.
مشكلة الترميز
يمكن نقل Sass و Javascript بسهولة باستخدام بيانات الاستيراد. تمنح لغات القوالب HTML نفس القدرة - يمكن للقوالب استيراد أجزاء أخرى من HTML في شكل أجزاء. يمكنك كتابة ترميز التذييل الخاص بك ، على سبيل المثال ، مرة واحدة فقط ، ثم تضمينه في قوالب أخرى. إن القول بوجود عدد كبير من لغات القوالب سيكون أمرًا بخسًا. تقييد نفسك بواحد فقط يحد بشدة من إمكانية إعادة استخدام التعليمات البرمجية الخاصة بك. البديل هو نسخ ولصق الترميز واستخدام NPM فقط للأنماط وجافا سكريبت.
هذا هو النهج الذي اتبعته Financial Times مع مكتبة مكونات Origami الخاصة بهم. في حديثها "ألا يمكنك جعله أكثر شبهاً ببوتستراب؟" خلصت أليس بارتليت إلى أنه "لا توجد طريقة جيدة للسماح للأشخاص بتضمين قوالب في مشاريعهم". في حديثه عن تجربته في الحفاظ على مكتبة مكونة في Lonely Planet ، كرر إيان فيذر مشاكل هذا النهج:
"بمجرد قيامهم بنسخ هذا الرمز ، فإنهم يقومون بشكل أساسي بقطع إصدار يحتاج إلى صيانة إلى أجل غير مسمى. عندما قاموا بنسخ الترميز لمكون عامل ، كان لديه ارتباط ضمني إلى لقطة من CSS في تلك المرحلة. إذا قمت بعد ذلك بتحديث النموذج أو إعادة بناء CSS ، فستحتاج إلى تحديث جميع إصدارات النموذج المنتشرة حول موقعك ".
الحل: مكونات الويب
تحل مكونات الويب هذه المشكلة عن طريق تحديد الترميز في JavaScript. مؤلف المكون مجاني في تعديل الترميز و CSS و Javascript. يمكن أن يستفيد مستهلك المكون من هذه الترقيات دون الحاجة إلى البحث في شفرة تعديل المشروع يدويًا. يمكن تحقيق المزامنة مع أحدث التغييرات على مستوى المشروع من خلال npm update
عبر المحطة الطرفية. يجب أن يظل اسم المكون وواجهة برمجة التطبيقات الخاصة به متسقًا فقط.
يعد تثبيت أحد مكونات الويب أمرًا بسيطًا مثل كتابة npm install component-name
في محطة طرفية. يمكن تضمين Javascript مع بيان الاستيراد:
<script type="module"> import './node_modules/component-name/index.js'; </script>
ثم يمكنك استخدام المكون في أي مكان في الترميز الخاص بك. فيما يلي مثال بسيط لمكون يقوم بنسخ النص إلى الحافظة.
شاهد العرض التوضيحي لمكون الويب Pen Simple بواسطة CSS GRID (cssgrid) على CodePen.
أصبح النهج المتمحور حول المكونات لتطوير الواجهة الأمامية موجودًا في كل مكان ، بشرح به إطار عمل React الخاص بـ Facebook. حتمًا ، نظرًا لانتشار الأطر في تدفقات العمل الأمامية الحديثة ، قام عدد من الشركات ببناء مكتبات مكونات باستخدام إطار العمل الذي تختاره. هذه المكونات قابلة لإعادة الاستخدام فقط ضمن هذا الإطار المحدد.

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


"لقد قمت ببناء تطبيقات ويب باستخدام: Dojo و Mootools و Prototype و jQuery و Backbone و Thorax و React على مر السنين ... أود أن أكون قادرًا على إحضار مكون Dojo القاتل الذي استخدمته معي إلى React تطبيق اليوم. "
- ديون المير ، مدير الهندسة ، جوجل
عندما نتحدث عن مكون ويب ، فإننا نتحدث عن دمج عنصر مخصص مع shadow DOM. تعد العناصر المخصصة و Shadow DOM جزءًا من مواصفات W3C DOM ومعيار WHATWG DOM - مما يعني أن مكونات الويب هي معيار ويب . تم تعيين العناصر المخصصة و Shadow DOM أخيرًا لتحقيق دعم عبر المتصفحات هذا العام. من خلال استخدام جزء قياسي من منصة الويب الأصلية ، نضمن أن تتمكن مكوناتنا من النجاة من الدورة سريعة الحركة لإعادة الهيكلة الأمامية وعمليات إعادة التفكير المعمارية. يمكن استخدام مكونات الويب مع أي لغة قوالب وأي إطار عمل أمامي - فهي متوافقة حقًا وقابلة للتشغيل المتبادل. يمكن استخدامها في كل مكان من مدونة Wordpress إلى تطبيق صفحة واحدة.

صنع مكون ويب
تحديد عنصر مخصص
كان من الممكن دائمًا تكوين أسماء العلامات وعرض محتواها على الصفحة.
<made-up-tag>Hello World!</made-up-tag>
تم تصميم HTML ليكون متسامحًا مع الأخطاء. سيتم عرض ما ورد أعلاه ، على الرغم من أنه ليس عنصر HTML صالحًا. لم يكن هناك أبدًا سبب وجيه للقيام بذلك - فقد كان الانحراف عن العلامات الموحدة تقليديًا ممارسة سيئة. ومع ذلك ، من خلال تحديد علامة جديدة باستخدام واجهة برمجة التطبيقات للعنصر المخصص ، يمكننا زيادة HTML بعناصر قابلة لإعادة الاستخدام تحتوي على وظائف مدمجة. يشبه إنشاء عنصر مخصص إلى حد كبير إنشاء مكون في React - ولكن هنا تم تمديد HTMLElement
.

class ExpandableBox extends HTMLElement { constructor() { super() } }
يجب أن تكون استدعاء super()
بدون معلمات هي العبارة الأولى في المُنشئ. يجب استخدام المُنشئ لإعداد الحالة الأولية والقيم الافتراضية ولإعداد أي مستمعين للأحداث. يجب تحديد عنصر مخصص جديد باسم علامة HTML الخاصة به والفئة المقابلة للعناصر:
customElements.define('expandable-box', ExpandableBox)
إنها اتفاقية لكتابة أسماء الفئات بأحرف كبيرة. ومع ذلك ، فإن صيغة علامة HTML هي أكثر من مجرد اصطلاح. ماذا لو أرادت المتصفحات تنفيذ عنصر HTML جديد وأرادوا تسميته صندوق قابل للتوسيع؟ لمنع تضارب التسمية ، لن تتضمن علامات HTML القياسية الجديدة شرطة. على النقيض من ذلك ، يجب أن تتضمن أسماء العناصر المخصصة شرطة.
customElements.define('whatever', Whatever) // invalid customElements.define('what-ever', Whatever) // valid
دورة حياة العنصر المخصص
تقدم واجهة برمجة التطبيقات أربعة تفاعلات للعناصر المخصصة - الوظائف التي يمكن تحديدها داخل الفئة التي سيتم استدعاؤها تلقائيًا استجابةً لأحداث معينة في دورة حياة عنصر مخصص.
يتم تشغيل connectCallback عند إضافة العنصر المخصص إلى DOM.
connectedCallback() { console.log("custom element is on the page!") }
يتضمن ذلك إضافة عنصر بجافا سكريبت:
document.body.appendChild(document.createElement("expandable-box")) //“custom element is on the page”
بالإضافة إلى تضمين العنصر داخل الصفحة بعلامة HTML:
<expandable-box></expandable-box> // "custom element is on the page"
يجب أن يكون أي عمل يتضمن جلب الموارد أو العرض هنا.
يتم تشغيل disconnectedCallback عند إزالة العنصر المخصص من DOM.
disconnectedCallback() { console.log("element has been removed") } document.querySelector("expandable-box").remove() //"element has been removed"
يتم تشغيل adoptedCallback
عند اعتماد العنصر المخصص في مستند جديد. ربما لا داعي للقلق بشأن هذا كثيرًا.
يتم تشغيل attributeChangedCallback
عند إضافة سمة أو تغييرها أو إزالتها. يمكن استخدامه للاستماع إلى التغييرات التي تطرأ على كل من السمات الأصلية الموحدة مثل المعطل أو src ، بالإضافة إلى أي سمات مخصصة نصنعها. يعد هذا أحد أقوى جوانب العناصر المخصصة لأنه يتيح إنشاء واجهة برمجة تطبيقات سهلة الاستخدام.
سمات العنصر المخصص
هناك العديد من سمات HTML. حتى لا يضيع المستعرض الوقت في استدعاء attributeChangedCallback
الخاصة بنا عند تغيير أي سمة ، نحتاج إلى تقديم قائمة بتغييرات السمات التي نريد الاستماع إليها. في هذا المثال ، نحن مهتمون بواحد فقط.
static get observedAttributes() { return ['expanded'] }
الآن ، سيتم استدعاء attributeChangedCallback
الخاصة بنا فقط عندما نغير قيمة السمة الموسعة على العنصر المخصص ، لأنها السمة الوحيدة التي قمنا بإدراجها.
يمكن أن تحتوي سمات HTML على قيم مقابلة (فكر في href و src و alt و value وما إلى ذلك) بينما تكون السمات الأخرى إما صحيحة أو خاطئة (على سبيل المثال ، معطلة ، محددة ، مطلوبة ). بالنسبة للسمة ذات القيمة المقابلة ، سنقوم بتضمين ما يلي في تعريف فئة العنصر المخصص.
get yourCustomAttributeName() { return this.getAttribute('yourCustomAttributeName'); } set yourCustomAttributeName(newValue) { this.setAttribute('yourCustomAttributeName', newValue); }
بالنسبة لعنصر المثال الخاص بنا ، ستكون السمة إما صحيحة أو خاطئة ، لذا فإن تعريف دالة getter و setter يختلف قليلاً.
get expanded() { return this.hasAttribute('expanded') } // the second argument for setAttribute is mandatory, so we'll use an empty string set expanded(val) { if (val) { this.setAttribute('expanded', ''); } else { this.removeAttribute('expanded') } }
الآن بعد أن تم التعامل مع النموذج المعياري ، يمكننا الاستفادة من السمة المميزة ( attributeChangedCallback
).
attributeChangedCallback(name, oldval, newval) { console.log(`the ${name} attribute has changed from ${oldval} to ${newval}!!`); // do something every time the attribute changes }
تقليديًا ، كان تكوين مكون جافا سكريبت يتضمن تمرير الوسائط إلى دالة init
. من خلال استخدام attributeChangedCallback
، من الممكن إنشاء عنصر مخصص قابل للتكوين فقط باستخدام الترميز.
يمكن استخدام Shadow DOM والعناصر المخصصة بشكل منفصل ، وقد تجد العناصر المخصصة مفيدة في حد ذاتها. على عكس Shadow DOM ، يمكن تعبئتها. ومع ذلك ، تعمل المواصفات بشكل جيد مع بعضها البعض.
إرفاق العلامات والأنماط باستخدام Shadow DOM
حتى الآن ، تعاملنا مع سلوك العنصر المخصص. ومع ذلك ، فيما يتعلق بالترميز والأنماط ، فإن العنصر المخصص لدينا يعادل <span>
غير منظم فارغًا. لتغليف HTML و CSS كجزء من المكون ، نحتاج إلى إرفاق ظل DOM. من الأفضل القيام بذلك داخل دالة المُنشئ.
class FancyComponent extends HTMLElement { constructor() { super() var shadowRoot = this.attachShadow({mode: 'open'}) shadowRoot.innerHTML = `<h2>hello world!</h2>` }
لا تقلق بشأن فهم ما يعنيه الوضع - يجب تضمين نموذجها المعياري ، لكنك سترغب دائمًا في open
. هذا المثال البسيط المكون سوف يعرض النص "hello world". مثل معظم عناصر HTML الأخرى ، يمكن أن يكون للعنصر المخصص توابع - ولكن ليس بشكل افتراضي. حتى الآن ، لن يعرض العنصر المخصص أعلاه الذي حددناه أي أطفال على الشاشة. لعرض أي محتوى بين العلامات ، نحتاج إلى استخدام عنصر slot
.
shadowRoot.innerHTML = ` <h2>hello world!</h2> <slot></slot> `
يمكننا استخدام علامة نمط لتطبيق بعض CSS على المكون.
shadowRoot.innerHTML = `<style> p { color: red; } </style> <h2>hello world!</h2> <slot>some default content</slot>`
سيتم تطبيق هذه الأنماط على المكون فقط ، لذلك نحن أحرار في استخدام محددات العناصر دون أن تؤثر الأنماط على أي شيء آخر في الصفحة. هذا يبسط كتابة CSS ، مما يجعل اصطلاحات التسمية مثل BEM غير ضرورية.
نشر مكون على NPM
يتم نشر حزم NPM عبر سطر الأوامر. افتح نافذة طرفية وانتقل إلى الدليل الذي ترغب في تحويله إلى حزمة قابلة لإعادة الاستخدام. ثم اكتب الأوامر التالية في الجهاز:
- إذا لم يكن مشروعك يحتوي بالفعل على package.json ،
npm init
واحدة. -
npm adduser
يربط جهازك بحساب NPM الخاص بك. إذا لم يكن لديك حساب موجود مسبقًا ، فسيتم إنشاء حساب جديد لك. -
npm publish

إذا سارت الأمور على ما يرام ، فلديك الآن مكون في سجل NPM ، جاهز للتثبيت واستخدامه في مشاريعك الخاصة - ومشاركته مع العالم.

واجهة برمجة تطبيقات مكونات الويب ليست مثالية. العناصر المخصصة غير قادرة حاليًا على تضمين البيانات في عمليات إرسال النموذج. قصة التحسين التدريجي ليست رائعة. التعامل مع إمكانية الوصول ليس سهلاً كما ينبغي.
على الرغم من الإعلان عنه في الأصل في عام 2011 ، إلا أن دعم المستعرض لا يزال غير شامل. من المقرر دعم Firefox في وقت لاحق من هذا العام. ومع ذلك ، فإن بعض المواقع البارزة (مثل Youtube) تستخدم هذه المواقع بالفعل. على الرغم من أوجه القصور الحالية في المكونات القابلة للمشاركة عالميًا ، فهي الخيار الوحيد وفي المستقبل يمكننا أن نتوقع إضافات مثيرة لما تقدمه.