سنتعلّم في هذا الدرس:
- بناء تطبيق نموذجي (مشاريب حسوب).
- إضافة وسائل تنقيح متطوّرة لتطبيقات Vue.js.
- المكوّنات المتداخلة.
- تسجيل المكوّنات محليًّا وتسجيلها على المستوى العام.
- تحديد المكوّن الذي اختاره المستخدم.
- التخاطب بين المكوّنات باستخدام أحداث مخصّصة.
سنتعلّم في هذا الدرس المزيد عن المكوّنات، حيث سنتعرّف على المزيد من المزايا المتعلّقة بها، والتي ستساعدنا على بناء مكوّنات عملية ومفيدة. سنبدأ هذا الدرس ببناء تطبيق نموذجي سيكون الهيكل الأساسي الذي سنستخدمه لتعلّم المزايا الجديدة حول المكوّنات، ثم سنتعرّف على كيفية استخدام أدوات تنقيح متطوّرة مكتوبة خصيصًا لـ Vue.js، ثم نتعرّف على المكوّنات المتداخلة وكيفية استخدامها، ثم سنتعلّم كيفية تسجيل المكوّنات محليًا وعلى المستوى العام في التطبيق، ونختم بتعلّم كيفية التخاطب بين المكوّنات المتداخلة.
بناء تطبيق نموذجي (مشاريب حسوب)
سنبني من أجل هذا الدرس تطبيق نموذجي بسيط لكي نطبق عليه الأفكار التي سنتاولها هنا. سيكون هذا التطبيق عبارة عن واجهة بسيطة لمحل افتراضي بيع مشروبات متنوعة. سيعبّر المكوّن في هذه المرة عن مشروب معيّن من قائمة المشروبات المتاحة.
انظر إلى الشكل النهائي المقترح:
كما ترى فإنّني أستخدم اللغة العربية في التطبيق هذه المرّة! أنشئ مجلّدًا جديدًا سمّه hsoub-drinks سنستخدمه لوضع ملفات المشروع ضمنه.
يحتوي هذا التطبيق على مكوّن وحيد حاليًا أسميته drink
أي مشروب ما. لهذا المكوّن خاصيّة وحيدة اسمها name
تعبّر عن اسم هذا المشروب. انظر إلى شيفرة Vue.js التي سأضعها ضمن الملف app.js الذي سيكون موجودًا ضمن المجلّد hsoub-drinks الذي أنشأته توًّا:
Vue.component('drink', { template: '#drink-template', props: { name: { type: String } } }); new Vue({ el: '#app', data: { drinks: ["شاي", "قهوة", "شاي أخضر", "زهورات", "بابونج"] } })
لاحظ كم هو بسيط هذا التطبيق: مكوّن عادي معرّف في الأعلى، وكائن Vue.js بسيط في الأسفل يحتوي على البيانات التي نريد عرضها على الشاشة. هذه البيانات موجودة ضمن المصفوفة drinks
كما هو واضح.
لننتقل الآن إلى شيفرة HTML التي سأضعها ضمن ملف سأسمّه index.html
وسيكون موجودًا أيضًا ضمن المجلّد hsoub-drinks:
<html lang="ar"> <body> <div class="header"> <span id="logo">مشاريب حسوب</span> </div> <div id="app" class="container"> <div class="content"> <h1 class="title">المشروبات المتوفرة</h1> <div class="drinks"> <drink v-for="drink in drinks" v-bind:name="drink"></drink> </div> </div> </div> <script type="text/x-template" id="drink-template"> <div class="drink"> <div class="description"> <span class="title"> {{name}} </span> </div> </div> </script> <script src="https://unpkg.com/vue@2.6.11/dist/vue.js"></script> <script src="app.js"></script> <link rel="stylesheet" href="app.css" /> </body> </html>
كما مرّ معنا فيما سبق نستخدم حلقة v-for
في المرور على عناصر المصفوفة drinks
وبالتالي توليد عنصر المكوّن drink
كما هو واضح من الشيفرة السابقة. لاحظ أيضًا أنّ العناصر المولَّدة والتي تمثّل المشروبات المتوفرة ستكون موجودة ضمن عنصر div
يحمل الصنف drinks
. أرجو أن لا يختلط عليك الأمر بين اسم المكوّن drink
وبين صنف التنسيق drink
لأنّ كليهما يحملان نفس الاسم، وهذا أمر جائز تمامًا.
بقيت أخيرًا تنسيقات CSS الذي سأضعها ضمن الملف app.css
ضعه أيضًا ضمن المجلد hsoub-drinks:
@import url(//fonts.googleapis.com/earlyaccess/notonaskharabic.css); body { height: 100vh; -webkit-font-smoothing: auto; -moz-osx-font-smoothing: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; font-family: 'Noto Naskh Arabic', serif; background-color: #ccdcdc; background-repeat: no-repeat; background-position: 100% 100% } span#logo { font-weight: 700; color: #eee; font-size: larger; letter-spacing: .05em; padding-left: .5rem; padding-right: .5rem; padding-bottom: 1rem; float: right; padding-top: 6px; margin-right: 20px; } .header{ background-color:slategray; width: 80% ; height: 50px; margin-left: auto; margin-right: auto; } h1.title { text-align: center; font-size: 1.875rem; font-weight: 500; color: #2d3336 } h2.subtitle { margin: 8px auto; font-size: x-large; text-align: center; line-height: 1.5; max-width: 500px; color: #5c6162 } .content { margin-left: auto; margin-right: auto; padding-top: 1.5rem; padding-bottom: 1.5rem; width: 620px } .drinks { padding: 0 40px; margin-bottom: 40px } .drinks .drink { background-color: #fff; -webkit-box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .1); box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .1); margin-top: 1rem; margin-bottom: 1rem; border-radius: .25rem; -webkit-box-pack: justify; -ms-flex-pack: justify; justify-content: space-between; cursor: pointer; position: relative; -webkit-transition: all .3s ease; transition: all .3s ease } .drinks .drink, .drinks .drink>.weight { display: -webkit-box; display: -ms-flexbox; display: flex } .drinks .drink>.description { width: 100%; padding: 1rem; } .drinks .drink>.description .title { color: #3d4852; display: block; font-weight: 700; margin-bottom: .25rem; float: right; } .drinks .drink>.description .description { font-size: .875rem; font-weight: 500; color: #8795a1; line-height: 1.5 } .drinks .drink>.price { width: 20%; color: #09848d; display: -webkit-box; display: -ms-flexbox; display: flex; padding-top: 1.5rem; font-family: Crimson Text, serif; font-weight: 600 } .drinks .drink>.price .dollar-sign { font-size: 24px; font-weight: 700 } .drinks .drink>.price .number { font-size: 72px; line-height: .5 } .drinks .active-drink, .drinks .drink:hover { -webkit-box-shadow: 0 15px 30px 0 rgba(0, 0, 0, .11), 0 5px 15px 0 rgba(0, 0, 0, .08); box-shadow: 0 15px 30px 0 rgba(0, 0, 0, .11), 0 5px 15px 0 rgba(0, 0, 0, .08) } .drinks .active-drink:after, .drinks .drink:hover:after { border-width: 2px; border-color: #7dacaf; border-radius: .25rem; content: ""; position: absolute; display: block; top: 0; bottom: 0; left: 0; right: 0 }
أصبح التطبيق النموذجي جاهزًا. نستطيع الآن المتابعة مع موضوعات هذا الدرس.
إضافة وسائل تنقيح متطوّرة لتطبيقات Vue.js
يوفّر مطوروا Vue.js أدوات تنقيح متطوّره مخصصة لتطبيقات Vue.js بحيث تسهّل حياة المبرمج إلى حدّ كبير. تظهر هذه الأدوات ضمن أدوات المطوّر التي يمكن الوصول إليها بضغط المفتاح F12
. يمكنك زيارة هذه الصفحة للاطلاع على كيفية تثبيت هذه الأدوات. بعد أن تثبت الأدوات السابقة، يمكنك الوصول إليها بضغط المفتاح F12 كما أشرنا، ثم تبحث عن لسان التبويب Vue.js (قد تحتاج إلى نقر زر عرض المزيد في حال لم تجد لسان التبويب هذا). انظر الشكل التالي لترى كيف تبدو.
المكونات المتداخلة
نحتاج في بعض الأحيان إلى جعل المكونات متداخلة فيما بينها، كأن يكون هناك مكوّن أساسي (مكوّن أب) يستخدم مكوّنات أصغر (مكوّنات أبناء) ضمنه. سنوظف هذا المفهوم ضمن التطبيق النموذجي السابق.
سأضيف مكوّنًا جديدًا ضمن الملف app.js وسأسمّه drink-selector
. سيلعب هذا المكوّن دور المكوّن الأساسي الذي يحوي المكوّن drink
الذي سيكون ضمنه بشكل متداخل. انظر إلى محتويات الملف app.js بعد إضافة المكوّن الجديد إليه:
Vue.component('drink', { template: '#drink-template', props: { name: { type: String } } }) Vue.component('drink-selector', { template: '#drink-selector-template', data() { return { drinks: ["شاي", "قهوة", "شاي أخضر", "زهورات", "بابونج"] } } }) new Vue({ el: '#app' })
لاحظ معي كيف نقلت البيانات الخاصة بالتطبيق من كائن Vue.js إلى المكوّن drink-selector
. لهذا الأمر فائدة سنراها لاحقًا إن شاء الله.
بالنسبة لشيفرة HTML فالأمر بسيط أيضًا. سننشئ القالب drink-selector-template
المعبّر عن مكوّننا الجديد. وسننقل إليه الشيفرة الخاصة بتوليد قائمة المشاريب. انظر محتوى القالب:
<script type="text/x-template" id="drink-selector-template"> <div class="drinks"> <drink v-for="drink in drinks" v-bind:name="drink"></drink> </div> </script>
بدلًا من الشيفرة التي نقلناها توًا يمكنك استخدام العنصر <drink-selector></drink-selector>
. انظر كيف ستصبح الشيفرة بعد التعديل الأخيرة على الملف index.html:
<html lang="ar"> <body> <div class="header"> <span id="logo">مشاريب حسوب</span> </div> <div id="app" class="container"> <div class="content"> <h1 class="title">المشروبات المتوفرة</h1> <drink-selector></drink-selector> </div> </div> <script type="text/x-template" id="drink-selector-template"> <div class="drinks"> <drink v-for="drink in drinks" :name="drink"></drink> </div> </script> <script type="text/x-template" id="drink-template"> <div class="drink"> <div class="description"> <span class="title"> {{name}} </span> </div> </div> </script> <script src="https://unpkg.com/vue@2.6.11/dist/vue.js"></script> <script src="app.js"></script> <link rel="stylesheet" href="app.css" /> </body> </html>
إذًا استطعنا استخدام مكوّن ضمن مكوّن آخر بشكل متداخل. يمكنك الآن أن تنشأ عناصر drink-selector
جديدة بنسخ ولصق السطر التالي بشكل متكرر:
<drink-selector></drink-selector>
تسجيل المكونات محليًّا وتسجيلها على المستوى العام
يمكن تسجيل المكوّنات ضمن تطبيق Vue.js بأسلوبين مختلفين. الأسلوب الأوّل هو الأسلوب العام، وهو الأسلوب الذي استخدمناه حتى هذه اللحظة باستخدام التابع Vue.component
. والأسلوب الآخر هو الأسلوب المحلي والذي سنتعرّف عليه في هذه الفقرة.
في الحقيقة لا يُعتبر تسجيل المكوّنات على المستوى العام أمرًا جيّدًا، لأنّه عندما ستكبر تطبيقاتك وتبدأ باستخدام أساليب متقدمة في بناء التطبيقات (كما سيمر معنا في الدروس اللاحقة)، سيتم بناء المكوّنات التي سجلتها على المستوى العام وذلك في التطبيق النهائي، حتى ولو لم تستخدمها في ذلك التطبيق. وهذا يعني زيادة في حجم شيفرة JavaScript التي على المستخدمين تحميلها من الانترنت دون فائدة. هذا فضلًا عن مشاكل من الناحية التصميمية للتطبيق. ففي تطبيقنا الأخير مثلًا، لن نستخدم المكوّن drink
خارج المكوّن drink-selector
، وبالتالي لا حاجة لتسجيل المكوّن drink
على المستوى العام.
لحل هذه المشكلة، وبالتالي تسجيل المكوّنات بشكل محلي إذا اقتضى الأمر ذلك، فيمكننا بكل بساطة تعريف المكوّن على شكل كائن JavaScript وإسناده إلى متغيّر عادي بدون استخدام التابع Vue.Component. ثم تسجيله في المكان الذي سنستخدمه فيه فقط. دعنا نطبّق هذه الطريقة على المكوّن drink
الذي سيصبح تعريفه على النحو التالي:
let drink_component = { template: '#drink-template', props: { name: { type: String } } }
بما أنّ هذا المكوّن سيُستخدم ضمن المكون drink-selector
لذلك سننشئ قسمًا جديدًا ضمن المكون drink-selector
اسمه components
والذي يسمح بتسجيل أي مكوّنات داخلية سيستخدمها هذا المكون. انظر إلى تعريف المكون drink-selector
بعد التعديل:
Vue.component('drink-selector', { template: '#drink-selector-template', components:{ drink: drink_component }, data() { return { drinks: ["شاي", "قهوة", "شاي أخضر", "زهورات", "بابونج"] } } })
وبذلك نكون قد سجّلنا المكوّن drink
محليّا ضمن المكوّن drink-selector
ولا يمكن بعد ذلك استخدام المكون drink
في أي مكان آخر غير المكون drink-selector
.
كما يمكن بطبيعة الحال فعل الأمر ذاته مع المكوّن drink-selector
أي تسجيله محليا بدون استخدم التابع Vue.component
في حال أردنا استخدام هذا المكوّن فقط ضمن صفحة محدّدة ضمن تطبيق الويب. سنجر الآن تعديلًا مماثلًا على المكوّن drink-selector
:
let drink_selector_component = { template: '#drink-selector-template', components:{ drink: drink_component }, data() { return { drinks: ["شاي", "قهوة", "شاي أخضر", "زهورات", "بابونج"] } } }
الآن، أين تعتقد أنّه يجب تسجيل المكوّن drink-selector
؟
الجواب بكل بساطة، وبما أنّه لا يوجد مكوّن رئيسي يمكن أن يُعرَّف المكوّن drink-selector
ضمنه، فسنسجله ضمن كائن Vue.js الخاص بالتطبيق باستخدام القسم components
أيضًا:
new Vue({ el: '#app', components:{ 'drink-selector': drink_selector_component } })
لاحظ معي أنّني قد عرفت اسم المكوّن هذه المرة على شكل نص: drink-selector
بشكل مختلف عن تعريف المكوّن drink
. السبب في ذلك أنّني أرغب بالاستمرار باستخدام الرمز -
ضمن اسم المكون drink-selector
وهذا جائز تمامًا بالطبع. إذا أردت الاطلاع على الشكل النهائي للملف app.js بعد التعديلات الأخيرة، انظر إلى الشيفرة التالية:
let drink_component = { template: '#drink-template', props: { name: { type: String } } } let drink_selector_component = { template: '#drink-selector-template', components:{ drink: drink_component }, data() { return { drinks: ["شاي", "قهوة", "شاي أخضر", "زهورات", "بابونج"] } } } new Vue({ el: '#app', components:{ 'drink-selector': drink_selector_component } })
تحديد المكون الذي اختاره المستخدم
لنمضي قدمًا في تطبيق المشاريب، حيث سنعمل الآن على إضافة ميزة تحديد المشروب الذي اختاره المستخدم عن طريق النقر عليه بالفأرة. تحتاج هذه الميزة إلى بعض المتطلّبات البسيطة، حيث سنُكسِب المكوّن drink
حقلًا جديدًا اسمه is_selected
سيُعرّف ضمن القسم data
كما نعلم، لتحديد فيما إذا كان المستخدم قد اختار هذا المشروب أم لا، بالإضافة إلى تزويده بتابع جديد يسمح باختيار المشروب ولنسمّه select
والذي سيحتوي على تعليمة برمجية واحدة تجعل قيمة الحقل is_selected
مساوية للقيمة true
، أي تعبّر عن عملية الاختيار. انظر كيف سيبدو شكل المكون drink
بعد إضافة التعديلين السابقين عليه:
let drink_component = { template: '#drink-template', props: { name: { type: String } }, data() { return { is_selected: false } }, methods: { select() { this.is_selected = true; } } }
لنجر الآن التعديلات اللازمة ضمن الملف index.html حيث سنضيف الموجّه v-on:click
ضمن قالب المكوّن drink
للاستجابة لحدث النقر. انظر التعديلات التالية على القالب drink-template
:
<script type="text/x-template" id="drink-template"> <div v-on:click="select" class="drink"> <div class="description"> <span class="title"> {{name}} </span> </div> </div> </script>
عد إلى المتصفّح لمعاينة التغييرات (تذكّر أنّنا نستعرض الملف index.html ضمن الخادوم Live Server). جرب أن تنقر على المشروبين: "شاي" و "شاي أخضر"، ثم انتقل إلى نافذة أدوات المطوّر بضغط F12، ومن هناك انتقل إلى لسان التبويب Vue. انشر العقد في حال احتجت ذلك، ستحصل على شكل شبيه بما يلي بالنسبة للمشروب الأول "شاي":
جرب أن تعاين حالة المشروب التالي "قهوة" ستجد أن قيمة الحقل is_selected
له تساوي false
لأنّنا لم ننقر على القهوة. جرب الآن أن تعاين حالة المشروب التالي "شاي أخضر"، ستلاحظ أنّ قيمة is_selected
في هذه الحالة هي true
. أي أنّ التطبيق حاليًا يعمل كما هو متوقّع منه حتى الآن. بقي أن نضيف بعض التأثيرات البصرية البسيطة للإشارة إلى أنّ المشروب قد اختير من قبل المستخدم. سأستخدم الموجّه v-bind:class
لإضافة تنسيق CSS وذلك ضمن القالب drink-template
على النحو التالي:
<script type="text/x-template" id="drink-template"> <div v-on:click="select" class="drink" v-bind:class="{'active-drink': is_selected}"> <div class="description"> <span class="title"> {{name}} </span> </div> </div> </script>
سيُطبّق تنسيق CSS المسمى 'active-drink'
فقط عندما تكون قيمة الحقل is_selected
تساوي true
أي عندما يختار المستخدم المشروب الحالي. ولاحاجة بطبيعة الحال لاستخدام علامة الاقتباس المفردة في حال لم يحتوي اسم الصنف على رمز مثل -
. بقي أن نضيف تنسيق CSS نفسه إلى ملف التنسيقات app.css. أضف الصنفين التاليين إلى ذلك الملف:
.drinks .active-drink, .drinks .drink:hover { background-color: lightgray; -webkit-box-shadow: 0 15px 30px 0 rgba(0, 0, 0, .11), 0 5px 15px 0 rgba(0, 0, 0, .08); box-shadow: 0 15px 30px 0 rgba(0, 0, 0, .11), 0 5px 15px 0 rgba(0, 0, 0, .08) } .drinks .active-drink:after, .drinks .drink:hover:after { border-width: 2px; border-color: #7dacaf; border-radius: .25rem; content: ""; position: absolute; display: block; top: 0; bottom: 0; left: 0; right: 0 }
عد إلى المتصفح واختر المشروب الأول والثالث مرة أخرى لتحصل على شكل شبيه بما يلي:
لقد تمّت المهمة بنجاح! ولكن، ماذا لو أردنا اختيار مشروب واحد فقط من القائمة؟ سنحل هذه المشكلة في الفقرة التالية حيث سنتعلّم كيفية التخاطب بين المكوّنات المختلفة باستخدام أحداث مخصّصة.
التخاطب بين المكونات باستخدام أحداث مخصصة
برزت الحاجة في الفقرة السابقة إلى اختيار مشروب واحد فقط من قائمة المشروبات المتاحة. أي أنّنا بحاجة إلى آلية معيّنة تسمح باختيار مشروب واحد فقط، بحيث تعمل هذه الآلية على إلغاء أي اختيار سابق لمشروب ما (في حال وجوده) ضمن القائمة، والإبقاء على المشروب الذي اختاره المستخدم أخيرًا.
يمكن تحقيق هذا الأمر بالتخاطب بين المكوّن الابن drink
والمكوّن الأب drink-selector
حيث سنستخدم المكوّن الأب للمساعدة في هذا الأمر.
يمكن التخاطب بين المكونات باستخدام الأحداث المخصّصة التي سنتحدث عنها بعد قليل، حيث سنبلغ المكوّن الأب drink-selector
بأن مشروبًا ما قد اختاره المستخدم، فيعمل عندئذ المكون الأب على إلغاء اختيار أي مشروب سابق يمكن أن يكون قد اختير من قبل، باستثناء المشروب الحالي.
قبل المتابعة، توقف عن القراءة قليلًا، حضر كوبًا من الشاي (أو القهوة)، ثم عد إلى هنا من جديد، لأنّ الشرح التالي يحتاج إلى المزيد من التركيز.
سنبدأ من الملف app.js حيث سنجري بعض التعديلات على المكونين drink
و drink-selector
. بالنسبة للمكون drink
سأجري التعديلات التالية:
استخدمنا في المثال في الفقرة السابقة الحقل is_selected
للإشارة إلى كون المشروب الحالي قد اختير أم لا. سأحول هذا الحقل إلى خاصية محسوبة (Computed Property) بالإضافة إلى أنّني سأحذف قسم data
بشكل كامل، وسنرى سبب ذلك بالإضافة بعد قليل. كما سأستخدم لأوّل مرة التابع المضمّن $emit
ضمن التابع select
(بدلًا من التعليمة this.is_selected = true
) لكي نُصدر الحدث drink_selected_event
وذلك لكي نبلّغ المكون الأب بأن المشروب الحالي قد اختير، وذلك بأن أمرّر اسم هذا المشروب كوسيط ثانٍ للتابع $emit
. سأضيف أيضًا خاصية جديدة اسمها selectedDrink
إلى المكون drink
ضمن القسم props
وذلك لكي أسمح للمكون الأب فيما بعد، بتمرير اسم المشروب (مكون drink
) الذي اختاره المستخدم حاليًا إلى هذا المكون.
تأمّل الشيفرة البرمجية الجديدة الخاصة بالمكون drink
:
let drink_component = { template: '#drink-template', props: { name: { type: String }, selectedDrink:{ type: String } }, computed:{ is_selected(){ return this.selectedDrink === this.name; } }, methods: { select() { this.$emit('drink_selected_event', this.name) } } }
أمّا بالنسبة للمكوّن drink-selector
فسأجري عليه أيضًا التعديلات التالية:
-
وضعت ضمنه القسم
methods
لكي أستطيع وضع التابعdrink_selected_handler
والذي سيكون معالجًا للحدثdrink_selected_event
الذي سأصدره (باستخدام التابع المضمّن $emit
) ضمن التابعselect
في المكوّنdrink
كما رأينا قبل قليل. -
أضفت حقلًا جديدًا أسميته
current_drink
ضمن القسمdata
وهو الذي سيحتفظ باسم المكوّن (المشروب) الذي اختاره المستخدم توًّا.
إليك الشيفرة البرمجية الجديدة الخاصة بالمكون drink-selector
:
let drink_selector_component = { template: '#drink-selector-template', components: { drink: drink_component }, data() { return { drinks: ["شاي", "قهوة", "شاي أخضر", "زهورات", "بابونج"], current_drink: null } }, methods:{ drink_selected_handler(drink_name){ this.current_drink = drink_name; } } }
إليك الآن التعديلات التي حدثت ضمن قالب المكوّن drink-selector
:
<script type="text/x-template" id="drink-selector-template"> <div class="drinks"> <drink v-for="drink in drinks" v-bind:name="drink" v-on:drink_selected_event="drink_selected_handler" v-bind:selectedDrink="current_drink"></drink> </div> </script>
أريد هنا التركيز على أمرين هما في صلب الموضوع:
-
الأمر الأوّل هو استخدامي للموجّه
v-on:drink_selected_event
. هذا الموجّه ليس مبيّتًا بالأصل. إنّما هو موجّه جديد مُحدَث بسبب استخدامي للتابع$emit
وتمريري للقيمة 'drink_selected_event' له. إذًا يمكن إنشاء موجّهات مخصّصة باستخدام التابع المبيّت $emit
. -
الأمر الثاني الذي أريد التركيز عليه هو الموجّه
v-bind:selectedDrink="current_drink"
الذي يربط قيمة الحقلcurrent_drink
من الموجّه الأب، مع الخاصيةselectedDrink
من الموجّه الابن.
فالذي يحدث بكل بساطة، هو أنّه عندما يختار المستخدم مشروبًا ما من القائمة، فسيُستدعى في البداية التابع select
من المكون الابن. ضمن هذا التابع لتنفّذ التعليمة البرمجية:
this.$emit('drink_selected_event', this.name)
التي تعمل على استدعاء حدث مخصص للتخاطب مع المكوّن الأب، حيث يقول المكوّن الابن للمكوّن الأب: "انظر لقد تمّ اختياري!"، علمًا أنّ اسم المكوّن الابن (اسم المشروب) سيمرّّر ضمن الوسيط الثاني من التابع $emit
كما أوضحنا قبل قليل.
بسبب وجود الموجّه v-on:drink_selected_event="drink_selected_handler"
ضمن قالب المكوّن الأب، سيُلتَقط هذا الحدث، وسيُستدعى التابع drink_selected_handler
من المكوّن الأب، حيث تعمل تعليمة بسيطة ضمنه على إسناد اسم المشروب (المكون الابن) الذي اختاره المستخدم ضمن الحقل current_drink
وبهذه الطريقة يعرف المكوّن الأب من هو المشروب الحالي الذي اختاره المستخدم.
ستؤدي هذه المعرفة، وبسبب طبيعة Vue.js إلى تحديث قالب المكوّن بكامله، وبالتالي ستنفّذ حلقة v-for
مرة أخرى، ولكن في هذه الحالة سيمرّر اسم المكوّن (المشروب) الذي اختاره المستخدم إلى جميع المشروبات الموجودة ضمن المكوّن الأب عن طريق الخاصية selectedDrink
وبالتالي سيعرف كل مكوّن ابن من المكوّن الابن "المحظوظ" الذي اختاره المستخدم حاليًا. وبسبب وجود الخاصية المحسوبة is_selected
في المكون الابن، ستنفّذ التعليمة البرمجية الموجودة ضمنها:
return this.selectedDrink === this.name;
هذه التعليمة بسيطة للغاية، وهي تُرجع قيمة من النوع المنطقي ture
إذا كان اسم المكوّن الحالي يطابق اسم المكوّن "المحظوظ" الذي اختاره المستخدم. أو تُرجع القيمة false
إذا لم تتحقق هذه المطابقة.
زبدة القول، فإنّ القيمة التي سترجعها الخاصية المحسوبة is_selected
هي التي ستُسند إلى الموجّه v-bind:class="{'active-drink': is_selected}"
وبالتالي يظهر هذا المشروب كما لو أنّه تم اختياره بتطبيق تنسيق CSS المناسب أو يظهر بشكل عادي.
ملاحظة نستنتج مما سبق، أنّه يمكن للمكوّن الأب أن يرسل رسائل إلى المكوّن الابن الموجود ضمنه، عن طريق الخصائص التي نعرفها ضمن القسم props
في المكون الابن، التي تُعتبر مستقبلات للمكون الابن تسمح له بالتقاط الرسائل من خارجه بشكل عام. أمّا عندما يريد المكون الابن مخاطبة المكون الأب، فيمكن ذلك عن طريق أحداث مخصّصة باستخدام التابع $emit
كما أوضحنا في المثال الأخير.
جرب التطبيق بعد التعديلات الأخيرة. لاحظ كيف أصبح يسمح باختيار مشروب واحد فقط.
ختامًا
لقد تناولنا في هذا الدرس الكثير من الموضوعات المهمة حول المكوّنات، وأعتقد أنّه أصبح من الضروري الانتقال إلى مستوى أعلى، عن طريق بناء تطبيقات بأساليب احترافية توفرها Vue.js لنا. سنعمل في الدرس القادم إن شاء الله على الاطلاع على كيفية فصل المكوّنات ضمن ملفات مستقلة تحتوي الواحدة منها على جميع التفاصيل المتعلّقة بالمكوّن مما يساهم بتنظيم الشيفرة البرمجية إلى حدّ كبير. بالإضافة إلى الاطلاع على تقنية بناء تطبيقات Vue.js بشكل مختلف عن طريق Vue.js CLI. حتى الدرس القادم أرجو أن تكونوا بأفضل حال!
اقرأ أيضًا
- المقال التالي: إنشاء مشاريع Vue.js باستخدام Vue CLI
- المقال السابق: مدخل إلى التعامل مع المكونات في Vue.js
- النسخة الكاملة لكتاب أساسيات إطار العمل Vue.js
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.