مكونات الويب: دليل المبتدئين



تعرف على فوائد استخدام مكونات الويب وكيفية عملها وكيفية البدء.



باستخدام مكونات الويب (يشار إليها فيما بعد باسم المكونات) ، يمكن للمطورين إنشاء عناصر HTML الخاصة بهم. في هذا الدليل ، ستتعلم كل ما يمكن معرفته عن المكونات. سنبدأ بماهية المكونات ، وما هي فوائدها ، وما هي مكوناتها.



بعد ذلك ، سنبدأ في بناء المكونات ، أولاً باستخدام قوالب HTML وواجهة DOM الظل ، ثم نتعمق في الموضوع قليلاً ونرى كيفية إنشاء عنصر بناء مخصص.



ما هي المكونات؟



يحب المطورون المكونات (نعني هنا تنفيذ نمط تصميم "الوحدة النمطية"). هذه طريقة رائعة لتحديد كتلة التعليمات البرمجية التي يمكن استخدامها في أي وقت وفي أي مكان. على مر السنين ، تم إجراء العديد من المحاولات الناجحة إلى حد ما لوضع هذه الفكرة موضع التنفيذ.



كانت لغة ربط XML من موزيلا ومواصفات مكونات HTML من Microsoft لبرنامج Internet Explorer 5 منذ حوالي 20 عامًا. لسوء الحظ ، كان كلا التطبيقين معقدين للغاية وفشلا في جذب اهتمام الشركات المصنعة للمتصفحات الأخرى ، وبالتالي سرعان ما تم نسيانهما. وعلى الرغم من ذلك ، فإنهم هم الذين وضعوا أسس ما لدينا في هذا المجال اليوم.



تتخذ أطر عمل JavaScript مثل React و Vue و Angular منهجًا مشابهًا. أحد الأسباب الرئيسية لنجاحها هو القدرة على تغليف المنطق العام للتطبيق في بعض القوالب التي تنتقل بسهولة من نموذج إلى آخر.



بينما تعمل هذه الأطر على تحسين تجربة التطوير ، فإن كل شيء يأتي بسعر. يجب تجميع ميزات اللغة مثل JSX ، وتستخدم معظم الأطر محرك JavaScript لإدارة تجريداتها. هل هناك طريقة أخرى لحل مشكلة تقسيم الكود إلى مكونات؟ الجواب هو مكونات الويب.



4 ركائز للمكونات



تتكون المكونات من ثلاث واجهات برمجة تطبيقات - عناصر مخصصة وقوالب HTML وظل DOM ، بالإضافة إلى وحدات JavaScript الأساسية (وحدات ES6). باستخدام الأدوات التي توفرها هذه الواجهات ، يمكنك إنشاء عناصر HTML مخصصة تتصرف مثل نظيراتها الأصلية.



يتم استخدام المكونات بنفس طريقة استخدام عناصر HTML العادية. يمكن تخصيصها باستخدام السمات ، واسترجاعها باستخدام JavaScript ، على غرار استخدام CSS. الشيء الرئيسي هو إخطار المتصفح بوجودها.



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



وتجدر الإشارة أيضًا إلى أن المكونات تتوافق مع معايير الويب. يعتمد الويب على فكرة التوافق مع الإصدارات السابقة. هذا يعني أن المكونات التي تم إنشاؤها اليوم ستعمل بشكل رائع لفترة طويلة.



دعنا نلقي نظرة على كل مواصفات على حدة.







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



دلائل الميزات:



  • تحديد سلوك العنصر
  • الرد على تغييرات السمات
  • توسيع العناصر الموجودة


غالبًا عندما يتحدث الناس عن المكونات ، فإنهم يقصدون واجهة العناصر المخصصة.



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



class ExampleElement extends HTMLElement {
  static get observedAttributes() {
      return [...]
  }
  attributeChangedCallback(name, oldValue, newValue) {}
  connectedCallback() {}
}
customElements.define('example-element', ExampleElement)


كل عنصر مخصص له هيكل مماثل. إنه يوسع وظائف فئة HTMLElements الحالية.



داخل عنصر مخصص ، توجد عدة طرق تسمى ردود الفعل المسؤولة عن معالجة تغيير معين على عنصر. على سبيل المثال ، يتم استدعاء connectCallback عند إضافة عنصر إلى الصفحة. هذا مشابه لمراحل دورة الحياة المستخدمة في أطر العمل (componentDidMount في React ، مُركب في Vue).



يتطلب تغيير سمات العنصر تغييرًا في سلوكه. عند حدوث تحديث ، يتم استدعاء attributeChangedCallback التي تحتوي على معلومات حول التغيير. يحدث هذا فقط للسمات المحددة في المصفوفة التي تم إرجاعها بواسطة السمات المرئية.



يجب تحديد العنصر قبل أن يتمكن المستعرض من استخدامه. تأخذ طريقة "تحديد" وسيطتين - اسم العلامة وفئتها. يجب أن تحتوي جميع العلامات على الحرف "-" لتجنب التعارض مع العناصر الأصلية الحالية والمستقبلية.



<example-element>Content</example-element>


يمكن استخدام العنصر كعلامة HTML عادية. عند العثور على مثل هذا العنصر ، يربط المتصفح سلوكه بالفئة المحددة. هذه العملية تسمى "الترقية".



هناك نوعان من العناصر - "مستقلة" و "مدمجة مخصصة". حتى الآن ، نظرنا في العناصر المستقلة. هذه هي العناصر التي لا تتعلق بعناصر HTML الموجودة. مثل علامتي div و span ، التي ليس لها معنى دلالي محدد.



تعمل العناصر المضمنة المخصصة - كما يوحي اسمها - على توسيع وظائف عناصر HTML الحالية. يرثون السلوك الدلالي لهذه العناصر ويمكنهم تغييره. على سبيل المثال ، إذا تم تخصيص عنصر "الإدخال" ، فسيظل حقل إدخال وجزءًا من النموذج عند إرساله.



class CustomInput extends HTMLInputElement {}
customElements.define('custom-input', CustomInput, { extends: 'input' })


تمد فئة العنصر المضمن المخصص فئة العنصر المخصص. عند تعريف عنصر مضمن ، يتم تمرير العنصر القابل للتوسيع باعتباره الوسيطة الثالثة.



<input is="custom-input" />


يختلف استخدام العلامة أيضًا قليلاً. بدلاً من علامة جديدة ، يتم استخدام العلامة الحالية ، مع تحديد سمة الامتداد الخاصة "is". عندما يصادف المستعرض هذه السمة ، فإنه يعلم أنه يتعامل مع عنصر مخصص ويقوم بتحديثه وفقًا لذلك.



بينما يتم دعم العناصر المستقلة بواسطة معظم المتصفحات الحديثة ، فإن العناصر المضمنة المخصصة مدعومة فقط بواسطة Chrome و Firefox. عند استخدامها في متصفح لا يدعمها ، سيتم التعامل معها كعناصر HTML عادية ، لذا فهي آمنة للاستخدام بشكل عام حتى في تلك المتصفحات.



2. قوالب HTML



  • إنشاء الهياكل الجاهزة
  • لا يتم عرضها على الصفحة قبل المكالمة
  • تحتوي على HTML و CSS و JS


من الناحية التاريخية ، تضمنت القوالب من جانب العميل سلسلة سلسلة في JavaScript أو استخدام مكتبات مثل Handlebars لتحليل كتل من العلامات المخصصة. في الآونة الأخيرة ، تحتوي المواصفات على علامة "template" يمكن أن تحتوي على كل ما نريد استخدامه.



<template id="tweet">
  <div class="tweet">
    <span class="message"></span>
      Written by @
    <span class="username"></span>
  </div>
</template>


في حد ذاته ، لا يؤثر على الصفحة بأي شكل من الأشكال ، أي لا يتم تحليلها بواسطة المحرك ، لا يتم إرسال طلبات الموارد (الصوت والفيديو). لا يمكن لـ JavaScript الوصول إليه ، وهو عنصر فارغ بالنسبة للمتصفحات.



const template = document.getElementById('tweet')
const node = document.importNode(template.content, true)
document.body.append(node)


أولاً نحصل على عنصر "النموذج". تنشئ طريقة importNode نسخة من محتواها ، أما الوسيطة الثانية (صواب) فتعني نسخة عميقة. أخيرًا ، نضيفه إلى الصفحة مثل أي عنصر آخر.



يمكن أن تحتوي القوالب على أي شيء يمكن أن يحتويه HTML العادي ، بما في ذلك CSS و JavaScript. عند إضافة عنصر إلى الصفحة ، سيتم تطبيق الأنماط عليه وسيتم تشغيل البرامج النصية. تذكر أن الأنماط والنصوص هي عامة ، مما يعني أنها تستطيع الكتابة فوق الأنماط والقيم الأخرى التي تستخدمها البرامج النصية.



القوالب لا تقتصر على هذا. تظهر بكل مجدها عند استخدامها مع أجزاء أخرى من المكونات ، لا سيما مع الظل DOM.



3. الظل DOM



  • يتجنب تعارضات الأسلوب
  • يصبح الخروج بأسماء (فئات ، على سبيل المثال) أسهل
  • تغليف منطق التنفيذ


نموذج كائن المستند (DOM) هو كيفية تفسير المستعرض لبنية الصفحة. من خلال قراءة الترميز ، يحدد المستعرض العناصر التي تحتوي على المحتوى ، وبناءً على ذلك ، يتخذ قرارًا بشأن ما يجب عرضه على الصفحة. عند استخدام document.getElemetById () ، على سبيل المثال ، يصل المتصفح إلى DOM للعثور على العنصر الذي يحتاجه.



بالنسبة إلى تخطيط الصفحة ، هذا جيد ، ولكن ماذا عن التفاصيل المخفية داخل العنصر؟ على سبيل المثال ، يجب ألا تهتم الصفحة بالواجهة المضمنة في عنصر "الفيديو". هذا هو المكان الذي يكون فيه Shadow DOM مفيدًا.



<div id="shadow-root"></div>
<script>
  const host = document.getElementById('shadow-root')
  const shadow = host.attachShadow({ mode: 'open' })
</script>


يتم إنشاء ظل DOM عند تطبيقه على عنصر. يمكن إضافة أي محتوى إلى shadow DOM ، تمامًا مثل DOM العادي ("light"). لا يتأثر ظل DOM بما يحدث في الخارج ، أي خارجه. لا يمكن لـ DOM العادي أيضًا الوصول إلى الظل مباشرةً. هذا يعني أنه في ظل DOM يمكننا استخدام أي أسماء للفئات وأنماط ونصوص ولا داعي للقلق بشأن التعارضات المحتملة.



يتم الحصول على أفضل النتائج باستخدام Shadow DOM جنبًا إلى جنب مع العناصر المخصصة. بفضل Shadow DOM ، عند إعادة استخدام أحد المكونات ، لا تؤثر أنماطه وبنيته على العناصر الأخرى على الصفحة بأي شكل من الأشكال.



وحدات ES و HTML


  • إضافة حسب الحاجة
  • لا يلزم التوليد المسبق
  • يتم تخزين كل شيء في مكان واحد


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



تحدد مواصفات استيراد HTML كيفية تصدير واستيراد مستندات HTML ، بالإضافة إلى CSS و JavaScript. سيسمح هذا بوضع العناصر المخصصة ، جنبًا إلى جنب مع القوالب و Shadow DOM ، في مكان آخر واستخدامها حسب الحاجة.



ومع ذلك ، رفض Firefox تنفيذ هذه المواصفات في متصفحه وقدم طريقة مختلفة بناءً على وحدات JavaScript.



export class ExampleElement external HTMLElement {}

import { ExampleElement } from 'ExampleElement.js'


الوحدات النمطية لها مساحة الاسم الخاصة بها افتراضيًا ، أي محتواها ليس عالميًا. يمكن استيراد المتغيرات والوظائف والفئات المصدرة في أي مكان وزمان واستخدامها كموارد محلية.



هذا يعمل بشكل رائع للمكونات. يمكن تصدير العناصر المخصصة التي تحتوي على قالب وظل DOM من ملف واستخدامها في ملف آخر.



import { ExampleElement } from 'ExampleElement.html'


تقدمت Microsoft باقتراح لتوسيع مواصفات وحدات JavaScript النمطية مع تصدير / استيراد HTML. سيسمح لك ذلك بإنشاء مكونات باستخدام HTML التعريفي والدلالي. ستتوفر هذه الميزة في Chrome و Edge قريبًا.



إنشاء المكون الخاص بك



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





تتيح لك المكونات عرض تعليقات المستخدم باستخدام قوالب HTML وواجهات DOM الظل.



لنقم بإنشاء مكون لعرض تعليقات المستخدم باستخدام قوالب HTML و shadow DOM.



1. إنشاء قالب


يحتاج المكون إلى قالب لنسخه قبل إنشاء العلامات. يمكن وضع القالب في أي مكان على الصفحة ، ويمكن لفئة العنصر المخصص الوصول إليه من خلال المعرف.



أضف عنصر "النموذج" إلى الصفحة. ستؤثر أي أنماط محددة في هذا العنصر عليه فقط.



<template id="user-comment-template">
  <style>
      ...
  </style>
</template>


2. إضافة الترميز


إلى جانب الأنماط ، يمكن أن يحتوي المكون على تخطيط (هيكل). لهذا الغرض ، يتم استخدام عنصر "div".



يتم تمرير المحتوى الديناميكي عبر الفتحات. أضف فتحات للصورة الرمزية والاسم ورسالة المستخدم بسمات "الاسم" المناسبة:



<div class="container">
  <div class="avatar-container">
    <slot name="avatar"></slot>
  </div>
  <div class="comment">
    <slot name="username"></slot>
    <slot name="comment"></slot>
  </div>
</div>


محتوى الفتحة الافتراضي




سيتم عرض المحتوى الافتراضي في حالة عدم وجود معلومات تم تمريرها إلى الفتحة ، وستحل



البيانات التي تم تمريرها إلى الفتحة محل البيانات الموجودة في القالب. إذا لم يتم تمرير أي معلومات إلى الفتحة ، فسيتم عرض المحتوى الافتراضي.



في هذه الحالة ، إذا لم يتم نقل اسم المستخدم ، فستظهر رسالة "No name" في مكانها:



<slot name="username">
  <span class="unknown">No name</span>
</slot>


3. إنشاء فئة


يبدأ إنشاء عنصر مخصص بتوسيع فئة "HTMLElement". جزء من عملية الإعداد هو إنشاء جذر ظل لعرض محتوى العنصر. نفتحه للوصول في الخطوة التالية.



أخيرًا ، نعلم المتصفح بفئة UserComment الجديدة.



class UserComment extends HTMLElement {
  constructor() {
      super()
      this.attachShadow({ mode: 'open' })
  }
}
customElements.define('user-comment', UserComment)


4. تطبيق محتوى الظل


عندما يواجه المستعرض عنصر "تعليق المستخدم" ، فإنه ينظر إلى عقدة جذر الظل لاسترداد محتواها. تخبر الوسيطة الثانية المتصفح أن ينسخ كل المحتوى ، وليس الطبقة الأولى فقط (عناصر المستوى الأعلى).



نضيف ترميزًا إلى عقدة جذر الظل ، والتي تقوم على الفور بتحديث مظهر المكون.



connectedCallback() {
  const template = document.getElementById('user-comment-template')
  const node = document.importNode(template.content, true)
  this.shadowRoot.append(node)
}


5. استخدام المكون


المكون جاهز الآن للاستخدام. أضف علامة "تعليق المستخدم" وقم بتمرير المعلومات الضرورية إليها.



نظرًا لأن جميع الفتحات لها أسماء ، فسيتم تجاهل أي شيء يتم تمريره خارجها. يتم نسخ كل شيء داخل الفتحات تمامًا كما تم تمريره ، بما في ذلك التصميم.



<user-comment>
  <img alt="" slot="avatar" src="avatar.png" />
  <span slot="username">Matt Crouch</span>
  <div slot="comment">This is an example of a comment</div>
</user-comment>


كود المثال الموسع:
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Web Components Example</title>
    <style>
      body {
        display: grid;
        place-items: center;
      }
      img {
        width: 80px;
        border-radius: 4px;
      }
    </style>
  </head>
  <body>
    <template id="user-comment-template">
      <div class="container">
        <div class="avatar-container">
          <slot name="avatar">
            <slot class="unknown"></slot>
          </slot>
        </div>
        <div class="comment">
          <slot name="username">No name</slot>
          <slot name="comment"></slot>
        </div>
      </div>
      <style>
        .container {
          width: 320px;
          clear: both;
          margin-bottom: 1rem;
        }
        .avatar-container {
          float: left;
          margin-right: 1rem;
        }
        .comment {
          height: 80px;
          display: flex;
          flex-direction: column;
          justify-content: center;
        }
        .unknown {
          display: block;
          width: 80px;
          height: 80px;
          border-radius: 4px;
          background: #ccc;
        }
      </style>
    </template>

    <user-comment>
      <img alt="" slot="avatar" src="avatar1.jpg" />
      <span slot="username">Matt Crouch</span>
      <div slot="comment">Fisrt comment</div>
    </user-comment>
    <user-comment>
      <img alt="" slot="avatar" src="avatar2.jpg" />
      <!-- no username -->
      <div slot="comment">Second comment</div>
    </user-comment>
    <user-comment>
      <!-- no avatar -->
      <span slot="username">John Smith</span>
      <div slot="comment">Second comment</div>
    </user-comment>

    <script>
      class UserComment extends HTMLElement {
        constructor() {
          super();
          this.attachShadow({ mode: "open" });
        }
        connectedCallback() {
          const template = document.getElementById("user-comment-template");
          const node = document.importNode(template.content, true);
          this.shadowRoot.append(node);
        }
      }
      customElements.define("user-comment", UserComment);
    </script>
  </body>
</html>










إنشاء عنصر مضمن مخصص



كما ذكرنا سابقًا ، يمكن للعناصر المخصصة توسيع العناصر الموجودة. هذا يوفر الوقت عن طريق الحفاظ على السلوك الافتراضي للعنصر الذي توفره الحضنة. في هذا القسم ، سنلقي نظرة على كيفية تمديد عنصر "الوقت".



1. إنشاء فئة


تظهر العناصر المضمنة ، مثل العناصر المستقلة ، عند توسيع الفئة ، ولكن بدلاً من الفئة العامة "HTMLElement" ، فإنها توسع فئة معينة.



في حالتنا هذه ، هذه الفئة هي HTMLTimeElement - الفئة التي تستخدمها عناصر "time". يتضمن السلوك المرتبط بسمة "التاريخ والوقت" ، بما في ذلك تنسيق البيانات.



class RelativeTime extends HTMLTimeElement {}


2. تعريف العنصر


يتم تسجيل العنصر بواسطة المتصفح باستخدام طريقة "تحديد". ومع ذلك ، على عكس العنصر المستقل ، عند تسجيل عنصر مضمن ، يجب تمرير طريقة "تحديد" إلى وسيط ثالث - كائن به إعدادات.



سيحتوي كائننا على مفتاح واحد بقيمة العنصر المخصص. يأخذ اسم العلامة. في حالة عدم وجود مثل هذا المفتاح ، سيتم طرح استثناء.



customElements.define('relative-time', RelativeTime, { extends: 'time' })


3. ضبط الوقت


نظرًا لأنه يمكن أن يكون لدينا عدة مكونات على الصفحة ، يجب أن يوفر المكون طريقة لتحديد قيمة العنصر. داخل هذه الطريقة ، يمرر المكون قيمة زمنية إلى مكتبة "timeago" ويضبط القيمة التي تعيدها تلك المكتبة على أنها قيمة العنصر (آسف على الحشو).



أخيرًا ، قمنا بتعيين سمة العنوان بحيث يمكن للمستخدم رؤية القيمة المعينة عند التمرير.



setTime() {
  this.innerHTML = timeago().format(this.getAttribute('datetime'))
  this.setAttribute('title', this.getAttribute('datetime'))
}


4. تحديث الاتصال


يمكن للمكون استخدام الطريقة مباشرة بعد عرضه على الصفحة. نظرًا لأن المكونات المضمنة لا تحتوي على ظل DOM ، فإنها لا تحتاج إلى مُنشئ.



connectedCAllback() {
  this.setTime()
}


5. تتبع تغيير السمات


إذا قمت بتحديث الوقت برمجيًا ، فلن يستجيب المكون. لا يعرف أنه يجب عليه مراقبة التغييرات في سمة "التاريخ والوقت".



بمجرد تحديد السمات المرصودة ، سيتم استدعاء attributeChangedCallback كلما تغيرت.



static get observedAttributes() {
  return ['datetime']
}
attributeChangedCallback() {
  this.setTime()
}


6. إضافة إلى الصفحة


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



<time is="relative-time" datetime="2020-09-20T12:00:00+0000">
  20  2020 . 12:00
</time>


كود المثال الموسع:
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Web Components Another Example</title>
    <!-- timeago.js -->
    <script
      src="https://cdnjs.cloudflare.com/ajax/libs/timeago.js/4.0.2/timeago.min.js"
      integrity="sha512-SVDh1zH5N9ChofSlNAK43lcNS7lWze6DTVx1JCXH1Tmno+0/1jMpdbR8YDgDUfcUrPp1xyE53G42GFrcM0CMVg=="
      crossorigin="anonymous"
    ></script>
    <style>
      body {
        display: flex;
        flex-direction: column;
        align-items: center;
      }
      input,
      button {
        margin-bottom: 0.5rem;
      }
      time {
        font-size: 2rem;
      }
    </style>
  </head>
  <body>
    <input type="text" placeholder="2020-10-20" value="2020-08-19" />
    <button>Set Time</button>

    <time is="relative-time" datetime="2020-09-19">
      19  2020 .
    </time>

    <script>
      class RelativeTime extends HTMLTimeElement {
        setTime() {
          this.innerHTML = timeago.format(this.getAttribute("datetime"));
          this.setAttribute("title", this.getAttribute("datetime"));
        }
        connectedCallback() {
          this.setTime();
        }
        static get observedAttributes() {
          return ["datetime"];
        }
        attributeChangedCallback() {
          this.setTime();
        }
      }
      customElements.define("relative-time", RelativeTime, { extends: "time" });

      const button = document.querySelector("button");
      const input = document.querySelector("input");
      const time = document.querySelector("time");

      button.onclick = () => {
        const { value } = input;
        time.setAttribute("datetime", value);
      };
    </script>
  </body>
</html>










آمل أن أكون قد ساعدتك في الحصول على فهم أساسي لمكونات الويب ، ولماذا تستخدم ، وكيف يتم استخدامها.



All Articles