مرحبا يا هابر!قررت مؤخرًا معرفة الشكل الذي يجب أن يبدو عليه تطبيق الويب من جانب العميل المكتوب بالكامل على مكونات ويب الفانيليا دون استخدام أطر العمل. اتضح أنه شيء جيد ، في النهاية قمت برسم قالب 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 متفق عليه ، إذا كنت تتولى وظيفة قياس الأداء (يمكنني القيام بذلك بشكل سيئ).شكرا للانتباه.