مكونات الويب بدلاً من رد الفعل - محاولة أخرى

مرحبا يا هابر!

قررت مؤخرًا معرفة الشكل الذي يجب أن يبدو عليه تطبيق الويب من جانب العميل المكتوب بالكامل على مكونات ويب الفانيليا دون استخدام أطر العمل. اتضح أنه شيء جيد ، في النهاية قمت برسم قالب PWA المحمول ، الذي أستخدمه الآن في النماذج الأولية. انطلاقا من المباني التالية:

  • DOM هي دولة. نظرًا لعدم وجود إطار عمل ، فإننا ننسى فورًا الوظيفة الوظيفية ونعود إلى OOP الحتمية. مكونات الويب هي عُقد DOM طويلة العمر التي تُغلف حالتها ولديها واجهة برمجة تطبيقات عامة. لم يتم إعادة إنشائها ، ولكن تم تغييرها. هذا يعني أنه يجب علينا أن ننظر إلى DOM ليس فقط على أنه تمثيل ، ولكن كمستودع لكائنات الأعمال ، وبالتالي نحن بحاجة إلى بناء تسلسل هرمي للمكونات مع مراعاة راحة تفاعلهم.
  • تفاعل المكونات. يمكن أن تتفاعل المكونات من خلال المكالمات المباشرة أو تبادل المكالمات أو من خلال أحداث DOM لأعلى / لأسفل للمستخدم. الطريقة الأخيرة هي الأكثر تفضيلاً ، لأنها تقلل من المشاركة المتبادلة (الاقتران) ، وتأمر بالرسم البياني للسندات (انظر المثال أدناه).
    تعمل أحداث DOM فقط داخل التسلسل الهرمي - يمكن أن تنبثق من الأسفل إلى أعلى سلسلة الأجداد ، أو البث إلى جميع الأحفاد. في حالات أخرى ، يتم استخدام واجهة برمجة التطبيقات (API) للمتصفح القياسي لمعالجة المكون: document.querySelector ('page-home') ، ويمكن لبعض المكونات تسجيل نفسها في النافذة واستخدامها عالميًا: APP.route ('page-login').
  • الأنماط الداخلية. لا يستخدم Shadow DOM ، لذلك ترث المكونات الأنماط العامة ، ولكن يمكن أن يكون لها أيضًا أنماطها الخاصة. نظرًا لأنه من غير المرجح أن يتم تنفيذ <style scoped> في المستقبل القريب ، يجب عليك استخدام بادئة اسم المكون للإعلان عن النمط الداخلي ، ولكن هذا قليل الكلام ويعمل بشكل جيد (انظر المثال أدناه).
  • التفاعل مع HTML / DOM. نظرًا لأن DOM عبارة عن حالة ، فإن مصدر البيانات هو قيم عناصر HTML نفسها (القيمة ، محددة ، innerHTML للمحتوى القابل للتحرير = "true" ، وما إلى ذلك). ليست هناك حاجة إلى متغيرات JS إضافية ، ولتسهيل الوصول إلى قيم النموذج - نحن فقط ننشئ الحروف / المستوطنين ، ونضيفها إلى كائن السلف (الذي يحتاج إلى مكتبة صغيرة). لا تختلف قيم نماذج العناوين الآن عن متغيرات فئة العنونة ، على سبيل المثال ، this.pass هي قيمة كلمة المرور التي تم إدخالها في التابع <input> التابع للمكون الحالي. وبالتالي ، لا يلزم وجود DOM افتراضي ولا ربط ثنائي الاتجاه ، كما أن إعادة رسم النماذج عند إعادة فتحها ليست ضرورية أيضًا ، ويتم حفظ البيانات التي تم إدخالها في النموذج أثناء التنقل ، ما لم تقم بتنظيفها على وجه التحديد.
  • التنقل. توجد مكونات الصفحة داخل حاوية <main> ، وبمجرد إنشائها ، لا يتم حذفها ، بل يتم إخفاؤها ببساطة. يتيح لك ذلك تنفيذ التنقل باستخدام location.hash ، وتعمل أزرار المتصفح القياسية ذهابًا وإيابًا بشكل صحيح. عند الانتقال إلى مكون موجود ، يتم استدعاء طريقة onRoute () ، حيث يمكنك تحديث البيانات.

هيكل التطبيق


يتكون طلبنا من:

  • المكون الجذر <app-app> ، الذي يمكن الوصول إليه عبر window.APP ، والذي يحتوي على موجه الصفحة والوظائف العالمية ؛
  • لوحات بأزرار سياقية (لم أضعها في مكون منفصل ، لكنني جعلتها جزءًا من تخطيط <app-app> لتبسيط معالجة الأحداث) ؛
  • القائمة المنسدلة (مكون منفصل) ؛
  • الحاوية <main> التي ستُضاف إليها مكونات الصفحة: <page-home> ، <page-login> ، <page-work> عند فتحها.

يتم تكديس الصفحات باستخدام التنقل ذهابًا وإيابًا. بالإضافة إلى ذلك ، نوضح تدفق البيانات من الأسفل إلى الأعلى ومن الأعلى إلى الأسفل:

  • يتم تخزين حالة التفويض واسم المستخدم الحالي في المكون <app-app> ، ولكنه يأتي من المكون <page-login> من خلال حدث منبثق.
  • يوضع جهاز ضبط الوقت في مكون <app-app> ، الذي يرسل القيمة الحالية لأسفل عن طريق حدث إذاعي يتم اعتراضه فقط في سلالة <page-work>.

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

التنفيذ


للعمل مع DOM الداخلي للمكون ، وإرسال الأحداث النهائية ، يتم استخدام مكتبة WcMixin.js الصغيرة - أقل من 200 سطر من التعليمات البرمجية ، يمكن أيضًا التخلص من نصفها (توحيد أحداث إدخال المستخدم). كل شيء آخر هو فانيليا نقية. يبدو المكون النموذجي (صفحة التفويض) كما يلي:

import wcmixin from './WcMixin.js'

const me = 'page-login'
customElements.define(me, class extends HTMLElement {
   _but = null

   connectedCallback() {
      this.innerHTML = `
         <style scoped>
            ${me} {
               height: 90%; width: 100%;
               display: flex; flex-direction: column;
               justify-content: center; align-items: center;
            }
            ${me} input { width: 60%; }
         </style>
         <input w-id='userInp/user' placeholder='user'/> 
         <input w-id='passInp/pass' type='password' placeholder='password'/>
      `
      wcmixin(this)

      this.userInp.oninput = (ev) => {
         this.bubbleEvent('login-change', {logged: false, user: this.user})
      }

      this.passInp.onkeypress = (ev) => {
         if (ev.key === 'Enter') this.login()
      }
   }

   onRoute() {
      this.userInp.focus()
      this._but = document.createElement('button')
      this._but.innerHTML = 'Log in<br>⇒'
      this._but.onclick = () => this.login()
      this.bubbleEvent('set-buts', { custom: [this._but] })
   }

   async login() {
      APP.msg = 'Authorization...'
      this._but.disabled = true
      setTimeout(() => {
         this._but.disabled = false
         if (this.user) {
            this.bubbleEvent('login-change', {logged: true, user: this.user})
            APP.route('page-work')
         } else {
            APP.msg = 'Empty user !'
            this.userInp.focus()
         }
      }, 1500)
   }
})

أولاً ، هنا نرى الأنماط المحلية للمكون. ثانيًا ، تمت إضافة السمة غير القياسية الوحيدة w-id = `` userInp / user '' إلى ترميز HTML. تعالج الدالة wcmixin () جميع العناصر المميزة بهذه السمة وتضيف متغيرات للمكون الحالي: يشير this.userInp إلى عنصر <input> نفسه (الذي يسمح لك بتعليق المعالج) ، وهذا this هو المستخدم الخاص بالعنصر (اسم المستخدم). إذا لم يكن الوصول إلى العنصر مطلوبًا ، فيمكنك تحديد w-id = `` / user '' ، وسيتم إنشاء القيمة فقط.

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

من المهم ألا يعرف مكون التفويض أي شيء عن المكونات الأعلى للتطبيق / اللوحة ، أي أنه غير مرتبط. يرسل ببساطة الأحداث في الطابق العلوي ، ومن يعترض عليها يعود إلى المطور. يتم تنفيذ استقبال الأحداث من تطبيق <app-app> في مكون <page-work> بنفس الطريقة:

import wcmixin from './WcMixin.js'

const me = 'page-work'
customElements.define(me, class extends HTMLElement {

   connectedCallback() {
      this.innerHTML = `
         <p w-id='/msg'>Enter text:</p>
         <p w-id='textDiv/text' contenteditable='true'>1)<br>2)<br>3)</p>
      `
      wcmixin(this)

      this.addEventListener('notify-timer', (ev) => {
         this.msg = `Enter text (elapsed ${ev.val}s):`
      })
   }

   async onRoute() {
      this.textDiv.focus()
      document.execCommand('selectAll',false,null)
      const but = document.createElement('button')
      but.innerHTML = 'Done<br>⇒'
      but.onclick = () => alert(this.text)
      this.bubbleEvent('set-buts', { custom: [but] })
   }
})

وفي مكون <app-app> نكتب:

setInterval(() => {
   this._elapsed += 1
   this.drownEvent('notify-timer', this._elapsed)
}, 1000)

لا يعرف المكون <app-app> أيضًا أي شيء عن مكونات الصفحة التي تريد استخدام العداد الخاص به ، أي أنه غير مرتبط بنسله. يكفي أن يوافق المطور على توقيعات الحدث. تعتبر أحداث DOM خفيفة الوزن ، ويتم إرسال الأحداث التنازلية فقط إلى مكونات الويب (وليس العناصر البسيطة) ، والأحداث الصاعدة تمر عبر سلسلة الأسلاف بأكملها كمعيار.

في الواقع ، هذا كل ما أردت قوله.

→  كود المشروع الكامل

اعتراضات واقتراحات


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

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

شكرا للانتباه.

All Articles