استخدام الفتحات في Vue.js
نشرت: 2022-03-10مع الإصدار الأخير من Vue 2.6 ، أصبح بناء الجملة الخاص باستخدام الفتحات أكثر إيجازًا. لقد جعلني هذا التغيير في الفتحات أعاد اهتمامي باكتشاف القوة المحتملة للفتحات لتوفير إمكانية إعادة الاستخدام ، والميزات الجديدة ، وإمكانية قراءة أوضح لمشاريعنا المستندة إلى Vue. ما الذي يمكن أن تقدمه ماكينات القمار حقًا؟
إذا كنت جديدًا على Vue أو لم ترَ التغييرات من الإصدار 2.6 ، فتابع القراءة. ربما يكون أفضل مورد للتعرف على الفتحات هو وثائق Vue الخاصة ، لكنني سأحاول تقديم ملخص هنا.
ما هي ماكينات القمار؟
الفتحات هي آلية لمكونات Vue تسمح لك بتكوين المكونات الخاصة بك بطريقة أخرى غير العلاقة الصارمة بين الوالدين والطفل. تمنحك الفتحات منفذًا لوضع المحتوى في أماكن جديدة أو جعل المكونات أكثر عمومية. أفضل طريقة لفهمها هي رؤيتها أثناء العمل. لنبدأ بمثال بسيط:
// frame.vue <template> <div class="frame"> <slot></slot> </div> </template>
هذا المكون يحتوي على غلاف div
. لنتخيل أن div
موجود لإنشاء إطار أسلوبي حول محتواه. يمكن استخدام هذا المكون بشكل عام لالتفاف إطار حول أي محتوى تريده. دعونا نرى كيف يبدو استخدامه. يشير مكون frame
هنا إلى المكون الذي صنعناه للتو أعلاه.
// app.vue <template> <frame><img src="an-image.jpg"></frame> </template>
سيتم إدراج المحتوى الموجود بين علامات frame
الفتح والإغلاق في مكون frame
حيث توجد slot
، لتحل محل علامات slot
. هذه هي الطريقة الأساسية للقيام بذلك. يمكنك أيضًا تحديد المحتوى الافتراضي للدخول إلى الفتحة ببساطة عن طريق ملئه:
// frame.vue <template> <div class="frame"> <slot>This is the default content if nothing gets specified to go here</slot> </div> </template>
الآن إذا استخدمناه مثل هذا بدلاً من ذلك:
// app.vue <template> <frame /> </template>
سيظهر النص الافتراضي لـ "هذا هو المحتوى الافتراضي إذا لم يتم تحديد أي شيء للذهاب هنا" ، ولكن إذا استخدمناه كما فعلنا من قبل ، فسيتم تجاوز النص الافتراضي بواسطة علامة img
.
فتحات متعددة / مسماة
يمكنك إضافة فتحات متعددة إلى أحد المكونات ، ولكن إذا قمت بذلك ، فيجب أن يكون لكل واحد منهم اسم. إذا كان هناك واحد بدون اسم ، فهذه هي الفتحة الافتراضية. إليك كيفية إنشاء فتحات متعددة:
// titled-frame.vue <template> <div class="frame"> <header><h2><slot name="header">Title</slot></h2></header> <slot>This is the default content if nothing gets specified to go here</slot> </div> </template>
لقد احتفظنا بنفس الفتحة الافتراضية ، لكننا هذه المرة أضفنا فتحة باسم header
حيث يمكنك إدخال عنوان. تستخدمه مثل هذا:
// app.vue <template> <titled-frame> <template v-slot:header> <!-- The code below goes into the header slot --> My Image's Title </template> <!-- The code below goes into the default slot --> <img src="an-image.jpg"> </titled-frame> </template>
تمامًا كما في السابق ، إذا أردنا إضافة محتوى إلى الفتحة الافتراضية ، فقط ضعه مباشرةً داخل مكون titled-frame
. لإضافة محتوى إلى فتحة مسماة ، على الرغم من ذلك ، احتجنا إلى التفاف الكود في علامة template
باستخدام توجيه v-slot
. يمكنك إضافة نقطتين ( :
بعد v-slot
ثم كتابة اسم الفتحة التي تريد تمرير المحتوى إليها. لاحظ أن v-slot
جديد في Vue 2.6 ، لذلك إذا كنت تستخدم إصدارًا أقدم ، فستحتاج إلى قراءة المستندات حول بنية الفتحة المهملة.
فتحات النطاق
هناك شيء آخر ستحتاج إلى معرفته وهو أن الفتحات يمكنها نقل البيانات / الوظائف إلى أطفالهم. لإثبات ذلك ، سنحتاج إلى مثال مكون مختلف تمامًا مع فتحات ، واحد أكثر اختراعًا من السابق: دعنا ننسخ المثال من المستندات عن طريق إنشاء مكون يوفر البيانات حول المستخدم الحالي إلى فتحاته:
// current-user.vue <template> <span> <slot v-bind:user="user"> {{ user.lastName }} </slot> </span> </template> <script> export default { data () { return { user: ... } } } </script>
هذا المكون له خاصية تسمى user
مع تفاصيل حول المستخدم. بشكل افتراضي ، يُظهر المكون الاسم الأخير للمستخدم ، ولكن لاحظ أنه يستخدم v-bind
لربط بيانات المستخدم بالفتحة. بذلك ، يمكننا استخدام هذا المكون لتوفير بيانات المستخدم لفرعها:
// app.vue <template> <current-user> <template v-slot:default="slotProps">{{ slotProps.user.firstName }}</template> </current-user> </template>
للوصول إلى البيانات التي تم تمريرها إلى الفتحة ، نحدد اسم متغير النطاق بقيمة توجيه v-slot
.
هناك بعض الملاحظات التي يجب تدوينها هنا:
- لقد حددنا الاسم
default
، على الرغم من أننا لسنا بحاجة إلى الفتحة الافتراضية. بدلاً من ذلك ، يمكننا فقط استخدامv-slot="slotProps"
. - لا تحتاج إلى استخدام
slotProps
كاسم. يمكنك أن نسميها ما تريد. - إذا كنت تستخدم فتحة افتراضية فقط ، فيمكنك تخطي علامة
template
الداخلية هذه ووضع توجيهv-slot
مباشرة على علامةcurrent-user
. - يمكنك استخدام إتلاف الكائنات لإنشاء مراجع مباشرة لبيانات الفتحة المحددة النطاق بدلاً من استخدام اسم متغير واحد. بمعنى آخر ، يمكنك استخدام
v-slot="{user}"
بدلاً منv-slot="slotProps"
وبعد ذلك يمكنك استخدامuser
مباشرةً بدلاً منslotProps.user
.
مع أخذ هذه الملاحظات في الاعتبار ، يمكن إعادة كتابة المثال أعلاه على النحو التالي:
// app.vue <template> <current-user v-slot="{user}"> {{ user.firstName }} </current-user> </template>
هناك عدة أشياء أخرى يجب وضعها في الاعتبار:
- يمكنك ربط أكثر من قيمة بتوجيهات
v-bind
. لذلك في المثال ، كان بإمكاني فعل أكثر من مجردuser
. - يمكنك تمرير الوظائف إلى الفتحات المحددة النطاق أيضًا. تستخدم العديد من المكتبات هذا لتوفير مكونات وظيفية قابلة لإعادة الاستخدام كما سترى لاحقًا.
-
v-slot
لها اسم مستعار من#
. لذا بدلاً من كتابةv-slot:header="data"
، يمكنك كتابة#header="data"
. يمكنك أيضًا تحديد#header
بدلاً منv-slot:header
عندما لا تستخدم الفتحات المحددة النطاق. بالنسبة إلى الفتحات الافتراضية ، ستحتاج إلى تحديد اسمdefault
عند استخدام الاسم المستعار. بمعنى آخر ، ستحتاج إلى كتابة#default="data"
بدلاً من#="data"
.
هناك بعض النقاط الثانوية التي يمكنك التعرف عليها من المستندات ، ولكن يجب أن يكون ذلك كافيًا لمساعدتك على فهم ما نتحدث عنه في بقية هذه المقالة.
ماذا يمكنك أن تفعل مع فتحات؟
لم يتم إنشاء الفتحات لغرض واحد ، أو على الأقل إذا كانت كذلك ، فقد تطورت بطريقة تتجاوز تلك النية الأصلية لتصبح أداة قوية للقيام بالعديد من الأشياء المختلفة.
أنماط قابلة لإعادة الاستخدام
تم تصميم المكونات دائمًا بحيث يمكن إعادة استخدامها ، ولكن بعض الأنماط ليست عملية لفرضها باستخدام مكون "عادي" واحد لأن عدد العناصر التي ستحتاجها props
قد يكون مفرطًا أو قد تحتاج إلى تمرير أقسام كبيرة من المحتوى والمكونات الأخرى المحتملة من خلال props
. يمكن استخدام الفتحات لتضمين الجزء "الخارجي" من النموذج والسماح بوضع HTML و / أو مكونات أخرى بداخلها لتخصيص الجزء "الداخلي" ، مما يسمح للمكون ذي الفتحات بتعريف النمط والمكونات المحقونة في فتحات لتكون فريدة من نوعها.
في مثالنا الأول ، لنبدأ بشيء بسيط: زر. تخيل أنك وفريقك تستخدمان Bootstrap *. باستخدام Bootstrap ، غالبًا ما يتم ربط الأزرار الخاصة بك بالفئة الأساسية `btn` وفئة تحدد اللون ، مثل` btn-basic`. يمكنك أيضًا إضافة فئة الحجم ، مثل `btn-lg`.
* أنا لا أشجعك ولا أحبطك من القيام بذلك ، أنا فقط بحاجة إلى شيء كمثال وهو معروف جيدًا.
لنفترض الآن ، من أجل التبسيط ، أن تطبيقك / موقعك يستخدم دائمًا btn-primary
basic و btn-lg
. لا تريد دائمًا أن تكتب كل الفئات الثلاثة على الأزرار الخاصة بك ، أو ربما لا تثق في أن يتذكر المبتدئ القيام بالأمور الثلاثة. في هذه الحالة ، يمكنك إنشاء مكون يحتوي تلقائيًا على جميع هذه الفئات الثلاثة ، ولكن كيف تسمح بتخصيص المحتوى؟ prop
ليست عملية لأنه يُسمح لعلامة button
بأن تحتوي على جميع أنواع HTML ، لذلك يجب علينا استخدام الفتحة.
<!-- my-button.vue --> <template> <button class="btn btn-primary btn-lg"> <slot>Click Me!</slot> </button> </template>
الآن يمكننا استخدامه في كل مكان مع أي محتوى تريده:

<!-- somewhere else, using my-button.vue --> <template> <my-button> <img src="/img/awesome-icon.jpg"> SMASH THIS BUTTON TO BECOME AWESOME FOR ONLY $500!!! </my-button> </template>
بالطبع ، يمكنك استخدام شيء أكبر بكثير من زر. التمسك بـ Bootstrap ، دعنا نلقي نظرة على مشروط ، أو على الأقل جزء HTML ؛ لن أخوض في الوظائف… حتى الآن.
<!-- my-modal.vue --> <template> <div class="modal" tabindex="-1" role="dialog"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <slot name="header"></slot> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> <slot name="body"></slot> </div> <div class="modal-footer"> <slot name="footer"></slot> </div> </div> </div> </div> </template>
الآن ، دعنا نستخدم هذا:
<!-- somewhere else, using my-modal.vue --> <template> <my-modal> <template #header><!-- using the shorthand for `v-slot` --> <h5>Awesome Interruption!</h5> </template> <template #body> <p>We interrupt your use of our application to let you know that this application is awesome and you should continue using it every day for the rest of your life!</p> </template> <template #footer> <em>Now back to your regularly scheduled app usage</em> </template> </my-modal> </template>
من الواضح أن نوع حالة الاستخدام أعلاه للفتحات مفيد جدًا ، ولكن يمكنه فعل المزيد.
إعادة استخدام الوظيفة
لا تدور مكونات Vue حول HTML و CSS. لقد تم إنشاؤها باستخدام JavaScript ، لذا فهي تتعلق أيضًا بالوظائف. يمكن أن تكون الفتحات مفيدة لإنشاء الوظائف مرة واحدة واستخدامها في أماكن متعددة. دعنا نعود إلى مثالنا الشرطي ونضيف وظيفة تغلق الشرطي:
<!-- my-modal.vue --> <template> <div class="modal" tabindex="-1" role="dialog"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <slot name="header"></slot> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> <slot name="body"></slot> </div> <div class="modal-footer"> <!-- using `v-bind` shorthand to pass the `closeModal` method to the component that will be in this slot --> <slot name="footer" :closeModal="closeModal"></slot> </div> </div> </div> </div> </template> <script> export default { //... methods: { closeModal () { // Do what needs to be done to close the modal... and maybe remove it from the DOM } } } </script>
الآن عند استخدام هذا المكون ، يمكنك إضافة زر إلى التذييل يمكنه إغلاق الشرطي. عادةً ، في حالة مشروط Bootstrap ، يمكنك فقط إضافة data-dismiss="modal"
إلى زر ، لكننا نريد إخفاء أشياء معينة في Bootstrap بعيدًا عن المكونات التي سيتم فتحها في هذا المكون الشرطي. لذلك قمنا بتمريرهم وظيفة يمكنهم الاتصال بها وليسوا أكثر حكمة بشأن مشاركة Bootstrap:
<!-- somewhere else, using my-modal.vue --> <template> <my-modal> <template #header><!-- using the shorthand for `v-slot` --> <h5>Awesome Interruption!</h5> </template> <template #body> <p>We interrupt your use of our application to let you know that this application is awesome and you should continue using it every day for the rest of your life!</p> </template> <!-- pull in `closeModal` and use it in a button's click handler --> <template #footer="{closeModal}"> <button @click="closeModal"> Take me back to the app so I can be awesome </button> </template> </my-modal> </template>
مكونات لا تجعله
وأخيرًا ، يمكنك أن تأخذ ما تعرفه عن استخدام الفتحات لتمرير الوظائف القابلة لإعادة الاستخدام وتجريد كل HTML عمليًا واستخدام الفتحات فقط. هذا أساسًا هو المكون الذي لا يقدم عرضًا: مكون يوفر وظائف فقط بدون أي HTML.
قد يكون إنشاء المكونات بلا عارض أمرًا صعبًا بعض الشيء لأنك ستحتاج إلى كتابة وظائف render
بدلاً من استخدام قالب لإزالة الحاجة إلى عنصر جذر ، ولكن قد لا يكون ذلك ضروريًا دائمًا. دعنا نلقي نظرة على مثال بسيط يتيح لنا استخدام قالب أولاً ، على الرغم من:
<template> <transition name="fade" v-bind="$attrs" v-on="$listeners"> <slot></slot> </transition> </template> <style> .fade-enter-active, .fade-leave-active { transition: opacity 0.3s; } .fade-enter, .fade-leave-to { opacity: 0; } </style>
هذا مثال غريب لمكون لا يعرض لأنه لا يحتوي حتى على أي جافا سكريبت فيه. هذا في الغالب لأننا نقوم فقط بإنشاء نسخة قابلة لإعادة الاستخدام مهيأة مسبقًا لوظيفة مضمنة بدون عرض: transition
.
نعم ، يحتوي Vue على مكونات عديمة العرض مضمنة. هذا المثال الخاص مأخوذ من مقال عن الانتقالات التي يمكن إعادة استخدامها بواسطة Cristi Jora ويوضح طريقة بسيطة لإنشاء مكون لا يُعرض يمكنه توحيد الانتقالات المستخدمة في جميع أنحاء التطبيق الخاص بك. تتعمق مقالة Cristi في مزيد من العمق وتُظهر بعض الاختلافات الأكثر تقدمًا في التحولات القابلة لإعادة الاستخدام ، لذلك أوصي بمراجعتها.
بالنسبة لمثالنا الآخر ، سننشئ مكونًا يتعامل مع تبديل ما يتم عرضه خلال حالات الوعد المختلفة: معلق ، وتم حله بنجاح ، وفشل. إنه نمط شائع وعلى الرغم من أنه لا يتطلب الكثير من التعليمات البرمجية ، إلا أنه يمكن أن يفسد الكثير من مكوناتك إذا لم يتم سحب المنطق لإعادة الاستخدام.
<!-- promised.vue --> <template> <span> <slot name="rejected" v-if="error" :error="error"></slot> <slot name="resolved" v-else-if="resolved" :data="data"></slot> <slot name="pending" v-else></slot> </span> </template> <script> export default { props: { promise: Promise }, data: () => ({ resolved: false, data: null, error: null }), watch: { promise: { handler (promise) { this.resolved = false this.error = null if (!promise) { this.data = null return } promise.then(data => { this.data = data this.resolved = true }) .catch(err => { this.error = err this.resolved = true }) }, immediate: true } } } </script>
إذا ماذا يجري هنا؟ أولاً ، لاحظ أننا نتلقى دعامة تسمى promise
وهو Promise
. في قسم watch
، نراقب التغييرات التي تطرأ على الوعد وعندما يتغير (أو فورًا عند إنشاء المكون بفضل الخاصية immediate
) نقوم بمسح الحالة ، then
ندعو catch
بالوعد ، ونقوم بتحديث الحالة عندما تنتهي بنجاح أو فشل.
بعد ذلك ، في القالب ، نعرض فتحة مختلفة بناءً على الحالة. لاحظ أننا فشلنا في إبقائه بلا عائد حقًا لأننا كنا بحاجة إلى عنصر جذر من أجل استخدام قالب. نقوم أيضًا بتمرير data
error
إلى نطاقات الفتحات ذات الصلة.
وإليك مثال على استخدامه:
<template> <div> <promised :promise="somePromise"> <template #resolved="{ data }"> Resolved: {{ data }} </template> <template #rejected="{ error }"> Rejected: {{ error }} </template> <template #pending> Working on it... </template> </promised> </div> </template> ...
نمرر في somePromise
إلى المكون الذي لا يصادفه. بينما ننتظر حتى ينتهي ، نعرض "جارٍ العمل عليه ..." بفضل الفتحة pending
. إذا نجحت ، فإننا نعرض "الحل:" وقيمة الدقة. إذا فشل ، نعرض "مرفوض:" والخطأ الذي تسبب في الرفض. الآن لم نعد بحاجة إلى تتبع حالة الوعد داخل هذا المكون لأن هذا الجزء يتم سحبه إلى مكونه القابل لإعادة الاستخدام.
لذا ، ما الذي يمكننا فعله حيال التفاف هذا span
حول الفتحات في promised.vue
؟ لإزالته ، سنحتاج إلى إزالة جزء template
وإضافة وظيفة render
إلى المكون الخاص بنا:
render () { if (this.error) { return this.$scopedSlots['rejected']({error: this.error}) } if (this.resolved) { return this.$scopedSlots['resolved']({data: this.data}) } return this.$scopedSlots['pending']() }
لا يوجد شيء صعب للغاية يحدث هنا. نحن نستخدم فقط بعض الكتل if
للعثور على الحالة ثم نعيد الفتحة ذات النطاق الصحيح (عبر this.$scopedSlots['SLOTNAME'](...)
) ونمرر البيانات ذات الصلة إلى نطاق الفتحة. عندما لا تستخدم نموذجًا ، يمكنك تخطي استخدام امتداد الملف .vue
عن طريق سحب جافا سكريبت من علامة script
وإدخالها في ملف .js
. يجب أن يمنحك هذا عثرة طفيفة جدًا في الأداء عند تجميع ملفات Vue هذه.
هذا المثال عبارة عن نسخة مجردة ومعدلة قليلاً من vue-promised ، والتي أوصي باستخدام المثال أعلاه لأنها تغطي بعض المزالق المحتملة. هناك الكثير من الأمثلة الرائعة الأخرى للمكونات التي لا تصدَّق هناك أيضًا. Baleada هي مكتبة كاملة مليئة بالمكونات التي لا تقدم والتي توفر وظائف مفيدة مثل هذه. هناك أيضًا vue-virtual-scroller للتحكم في عرض عنصر القائمة بناءً على ما هو مرئي على الشاشة أو PortalVue لنقل المحتوى إلى أجزاء مختلفة تمامًا من DOM.
أنا بالخارج
تأخذ فتحات Vue التطوير المستند إلى المكونات إلى مستوى جديد تمامًا ، وعلى الرغم من أنني أظهرت الكثير من الطرق الرائعة التي يمكن من خلالها استخدام الفتحات ، إلا أن هناك عددًا لا يُحصى من الميزات الأخرى. ما هي الفكرة الرائعة التي يمكن أن تفكر بها؟ ما هي الطرق التي تعتقد أنها يمكن أن تحصل على ترقية للفتحات؟ إذا كان لديك أي شيء ، فتأكد من تقديم أفكارك إلى فريق Vue. بارك الله وسعداء الترميز.