Vue.js'de Yuvaları Kullanma
Yayınlanan: 2022-03-10Vue 2.6'nın son sürümüyle, yuvaları kullanmak için sözdizimi daha özlü hale getirildi. Slotlardaki bu değişiklik, Vue tabanlı projelerimize yeniden kullanılabilirlik, yeni özellikler ve daha net okunabilirlik sağlamak için slotların potansiyel gücünü keşfetme konusunda yeniden ilgi duymamı sağladı. Slotlar gerçekten neler yapabilir?
Vue'da yeniyseniz veya 2.6 sürümündeki değişiklikleri görmediyseniz, okumaya devam edin. Muhtemelen slotlar hakkında bilgi edinmek için en iyi kaynak Vue'nin kendi belgeleridir, ancak burada bir özet vermeye çalışacağım.
Slotlar Nelerdir?
Yuvalar, bileşenlerinizi katı ebeveyn-alt ilişkisi dışında bir şekilde oluşturmanıza olanak tanıyan Vue bileşenleri için bir mekanizmadır. Yuvalar, içeriği yeni yerlere yerleştirmeniz veya bileşenleri daha genel hale getirmeniz için size bir çıkış sağlar. Onları anlamanın en iyi yolu onları çalışırken görmektir. Basit bir örnekle başlayalım:
// frame.vue <template> <div class="frame"> <slot></slot> </div> </template>
Bu bileşen bir sarmalayıcı div
sahiptir. Diyelim ki div
, içeriği etrafında stilistik bir çerçeve oluşturmak için orada. Bu bileşen, istediğiniz herhangi bir içeriğin etrafına bir çerçeve sarmak için genel olarak kullanılabilir. Bakalım onu kullanmak nasıl görünüyor. Buradaki frame
bileşeni, az önce yukarıda yaptığımız bileşeni ifade eder.
// app.vue <template> <frame><img src="an-image.jpg"></frame> </template>
Açılış ve kapanış frame
etiketleri arasındaki içerik, slot
etiketlerinin yerini alarak slot
bulunduğu frame
bileşenine eklenir. Bunu yapmanın en temel yolu budur. Ayrıca, bir yuvaya girmek için varsayılan içeriği doldurarak da belirtebilirsiniz:
// frame.vue <template> <div class="frame"> <slot>This is the default content if nothing gets specified to go here</slot> </div> </template>
Şimdi bunun yerine böyle kullanırsak:
// app.vue <template> <frame /> </template>
“Buraya gitmek için hiçbir şey belirtilmediyse varsayılan içerik budur” varsayılan metni görünecektir, ancak daha önce yaptığımız gibi kullanırsak, varsayılan metin img
etiketi tarafından geçersiz kılınacaktır.
Çoklu/Adlandırılmış Yuvalar
Bir bileşene birden çok yuva ekleyebilirsiniz, ancak bunu yaparsanız, biri dışında hepsinin bir ada sahip olması gerekir. Adsız bir tane varsa, varsayılan yuvadır. Birden çok yuvayı şu şekilde oluşturabilirsiniz:
// 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>
Aynı varsayılan alanı koruduk, ancak bu sefer header
girebileceğiniz başlık adlı bir yuva ekledik. Bunu şu şekilde kullanırsın:
// 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>
Daha önce olduğu gibi, varsayılan yuvaya içerik eklemek istiyorsak, onu doğrudan titled-frame
bileşeninin içine koymanız yeterlidir. Ancak, adlandırılmış bir yuvaya içerik eklemek için, kodu bir v-slot
yönergesiyle bir template
etiketine sarmamız gerekiyordu. v-slot
sonra iki nokta üst üste ( :
) ekler ve ardından içeriğin iletilmesini istediğiniz yuvanın adını yazarsınız. v-slot
Vue 2.6'da yeni olduğunu unutmayın, bu nedenle daha eski bir sürüm kullanıyorsanız, kullanımdan kaldırılmış yuva sözdizimi hakkındaki belgeleri okumanız gerekir.
Kapsamlı Yuvalar
Bilmeniz gereken bir şey daha, slotların verileri/işlevleri çocuklarına aktarabilmesidir. Bunu göstermek için, yuvaları olan tamamen farklı bir örnek bileşene ihtiyacımız olacak, bir öncekinden daha yapmacık olan bir bileşen: mevcut kullanıcı hakkındaki verileri yuvalarına sağlayan bir bileşen oluşturarak örneği dokümanlardan kopyalayalım:
// current-user.vue <template> <span> <slot v-bind:user="user"> {{ user.lastName }} </slot> </span> </template> <script> export default { data () { return { user: ... } } } </script>
Bu bileşen, kullanıcıyla ilgili ayrıntıları içeren user
adlı bir özelliğe sahiptir. Varsayılan olarak, bileşen kullanıcının soyadını gösterir, ancak kullanıcı verilerini yuvaya bağlamak için v-bind
kullandığını unutmayın. Bununla, kullanıcı verilerini kendi soyundan gelenlere sağlamak için bu bileşeni kullanabiliriz:
// app.vue <template> <current-user> <template v-slot:default="slotProps">{{ slotProps.user.firstName }}</template> </current-user> </template>
Slota iletilen verilere erişim sağlamak için v-slot
yönergesinin değeri ile kapsam değişkeninin adını belirtiriz.
Burada alınması gereken birkaç not var:
- Varsayılan yuva için ihtiyacımız olmasa da
default
adını belirledik. Bunun yerine sadecev-slot="slotProps"
kullanabiliriz. - Ad olarak
slotProps
kullanmanıza gerek yoktur. Ne istersen onu arayabilirsin. - Yalnızca varsayılan bir yuva kullanıyorsanız, bu iç
template
etiketini atlayabilir vev-slot
yönergesini doğrudancurrent-user
etiketine koyabilirsiniz. - Kapsamlı yuva verilerine doğrudan referanslar oluşturmak için tek bir değişken adı kullanmak yerine nesne yok etmeyi kullanabilirsiniz. Başka bir deyişle,
v-slot="slotProps"
yerinev-slot="{user}"
kullanabilir ve ardındanslotProps.user
yerine doğrudanuser
kullanabilirsiniz.
Bu notları dikkate alarak yukarıdaki örnek şu şekilde yeniden yazılabilir:
// app.vue <template> <current-user v-slot="{user}"> {{ user.firstName }} </current-user> </template>
Akılda tutulması gereken birkaç şey daha:
-
v-bind
yönergeleri ile birden fazla değeri bağlayabilirsiniz. Yani örnekte,user
'dan daha fazlasını yapabilirdim. - Kapsamlı yuvalara da işlevler iletebilirsiniz. Birçok kitaplık bunu daha sonra göreceğiniz gibi yeniden kullanılabilir işlevsel bileşenler sağlamak için kullanır.
-
v-slot
takma adı#
. Yaniv-slot:header="data"
yazmak yerine#header="data"
yazabilirsiniz. Kapsamlı yuvaları kullanmadığınızdav-slot:header
yerine sadece#header
belirtebilirsiniz. Varsayılan yuvalara gelince, takma adı kullandığınızdadefault
adını belirtmeniz gerekir. Başka bir deyişle, #="data" yerine#="data"
#default="data"
yazmanız gerekecek.
Dokümanlardan öğrenebileceğiniz birkaç küçük nokta daha var, ancak bu makalenin geri kalanında neden bahsettiğimizi anlamanıza yardımcı olmak için yeterli olacaktır.
Slotlarla Neler Yapabilirsiniz?
Slotlar tek bir amaç için inşa edilmedi ya da en azından öyle olsaydı, birçok farklı şey yapmak için bir güç merkezi aracı olmak için bu orijinal niyetin çok ötesinde geliştiler.
Yeniden Kullanılabilir Desenler
Bileşenler her zaman yeniden kullanılabilecek şekilde tasarlanmıştır, ancak bazı kalıpları tek bir "normal" bileşenle uygulamak pratik değildir çünkü özelleştirmek için ihtiyaç duyacağınız props
sayısı aşırı olabilir veya buna ihtiyacınız olabilir. içeriğin büyük bölümlerini ve potansiyel olarak diğer bileşenleri props
. Yuvalar, kalıbın "dış" kısmını kapsamak ve "iç" kısmı özelleştirmek için diğer HTML ve/veya bileşenlerin içlerine yerleştirilmesine izin vermek için kullanılabilir, bu da yuvalara sahip bileşenin kalıbı ve içine enjekte edilen bileşenleri tanımlamasına izin verir. benzersiz olmak için yuvalar.
İlk örneğimiz için basit bir şeyle başlayalım: bir düğme. Sizin ve ekibinizin Bootstrap* kullandığınızı hayal edin. Bootstrap ile, düğmeleriniz genellikle temel "btn" sınıfıyla ve "btn-birincil" gibi rengi belirten bir sınıfla bağlanır. "btn-lg" gibi bir beden sınıfı da ekleyebilirsiniz.
* Bunu yapmanız için sizi ne cesaretlendiriyorum ne de cesaretlendiriyorum, sadece benim örneğim için bir şeye ihtiyacım vardı ve bu oldukça iyi biliniyor.
Şimdi, basitlik adına, uygulamanızın/sitenizin her zaman btn-primary
ve btn-lg
kullandığını varsayalım. Düğmelerinize her zaman üç sınıfın tümünü yazmak zorunda kalmak istemezsiniz ya da belki bir çaylakın üçünü de yapmayı hatırlayacağına güvenmiyorsunuzdur. Bu durumda, bu sınıfların üçüne de otomatik olarak sahip olan bir bileşen oluşturabilirsiniz, ancak içeriğin özelleştirilmesine nasıl izin veriyorsunuz? Bir prop
, pratik değildir çünkü bir button
etiketinin içinde her tür HTML'ye sahip olmasına izin verilir, bu yüzden bir yuva kullanmalıyız.
<!-- my-button.vue --> <template> <button class="btn btn-primary btn-lg"> <slot>Click Me!</slot> </button> </template>
Artık istediğiniz içerikle her yerde kullanabiliriz:
<!-- 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>
Tabii ki, bir düğmeden çok daha büyük bir şeyle gidebilirsiniz. Bootstrap'a bağlı kalarak, bir modala veya en azından HTML kısmına bakalım; İşlevselliğe girmeyeceğim… henüz.
<!-- 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>
Şimdi bunu kullanalım:
<!-- 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>
Slotlar için yukarıdaki kullanım durumu açıkça çok faydalıdır, ancak daha fazlasını da yapabilir.
İşlevselliği Yeniden Kullanma
Vue bileşenleri tamamen HTML ve CSS ile ilgili değildir. JavaScript ile oluşturuldukları için işlevsellikle de ilgilidirler. Yuvalar, bir kez işlevsellik oluşturmak ve onu birden çok yerde kullanmak için yararlı olabilir. Modal örneğimize geri dönelim ve kipi kapatan bir fonksiyon ekleyelim:
<!-- 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>
Şimdi bu bileşeni kullandığınızda, altbilgiye kipi kapatabilecek bir düğme ekleyebilirsiniz. Normalde, bir Bootstrap modeli söz konusu olduğunda, bir düğmeye data-dismiss="modal"
ekleyebilirsiniz, ancak Bootstrap'e özgü şeyleri bu kalıcı bileşene yerleştirilecek bileşenlerden uzağa gizlemek istiyoruz. Bu yüzden onlara arayabilecekleri bir işlev iletiyoruz ve Bootstrap'ın katılımı konusunda daha akıllı değiller:
<!-- 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>
Renderless Bileşenler
Ve son olarak, yeniden kullanılabilir işlevselliği dolaşmak ve neredeyse tüm HTML'yi çıkarmak için yuvaları kullanma hakkında bildiklerinizi alabilir ve yalnızca yuvaları kullanabilirsiniz. Esasen renderless bileşeni budur: HTML olmadan yalnızca işlevsellik sağlayan bir bileşen.
Bileşenleri gerçekten rendersiz hale getirmek biraz zor olabilir çünkü bir kök eleman ihtiyacını ortadan kaldırmak için bir şablon kullanmak yerine render
fonksiyonları yazmanız gerekecek, ancak bu her zaman gerekli olmayabilir. Yine de önce bir şablon kullanmamıza izin veren basit bir örneğe bakalım:
<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>
Bu, rendersiz bileşenin tuhaf bir örneğidir çünkü içinde JavaScript bile yoktur. Bunun nedeni, yalnızca yerleşik bir oluşturmasız işlevin önceden yapılandırılmış yeniden kullanılabilir bir sürümünü oluşturmamızdır: transition
.
Evet, Vue yerleşik rendersiz bileşenlere sahiptir. Bu özel örnek, Cristi Jora'nın yeniden kullanılabilir geçişler hakkındaki bir makalesinden alınmıştır ve uygulamanız boyunca kullanılan geçişleri standartlaştırabilen rendersiz bir bileşen oluşturmanın basit bir yolunu gösterir. Cristi'nin makalesi çok daha derinlere iniyor ve yeniden kullanılabilir geçişlerin bazı daha gelişmiş varyasyonlarını gösteriyor, bu yüzden incelemenizi tavsiye ederim.
Diğer örneğimiz için, bir Sözün farklı durumları sırasında gösterilenleri değiştirmeyi işleyen bir bileşen oluşturacağız: beklemede, başarıyla çözüldü ve başarısız oldu. Bu yaygın bir kalıptır ve çok fazla kod gerektirmese de mantık yeniden kullanılabilirlik için çıkarılmazsa birçok bileşeninizi bulandırabilir.
<!-- 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>
Peki burada neler oluyor? İlk olarak, bir Promise
olan promise
adı verilen bir destek aldığımızı unutmayın. watch
bölümünde, sözdeki değişiklikleri izliyoruz ve değiştiğinde (veya immediate
özellik sayesinde bileşen oluşturma anında) durumu temizliyoruz ve then
çağırıyoruz ve sözü catch
, durumu başarılı bir şekilde bittiğinde veya bittiğinde durumu güncelliyoruz. başarısız olur.
Ardından, şablonda duruma göre farklı bir yuva gösteriyoruz. Bir şablonu kullanmak için bir kök öğeye ihtiyacımız olduğundan, onu gerçekten rendersiz tutmayı başaramadığımızı unutmayın. data
ve error
ilgili slot kapsamlarına da aktarıyoruz.
Ve işte kullanıldığına bir örnek:
<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
renderless bileşenine iletiyoruz. Bitmesini beklerken, pending
yuva sayesinde “Üzerinde Çalışıyor…” mesajını görüntülüyoruz. Başarılı olursa, “Resolved:” ve çözünürlük değerini görüntüleriz. Başarısız olursa, “Reddedildi:” ve reddedilmeye neden olan hatayı görüntüleriz. Artık bu bileşen içindeki vaadin durumunu izlememize gerek yok çünkü o kısım kendi yeniden kullanılabilir bileşenine çekildi.
Peki, promised.vue
verilen.vue içindeki yuvaların etrafındaki bu span
hakkında ne yapabiliriz? Kaldırmak için template
kısmını kaldırmamız ve bileşenimize bir render
işlevi eklememiz gerekecek:
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']() }
Burada çok zor bir şey olmuyor. Durumu bulmak için sadece bazı if
blokları kullanıyoruz ve ardından doğru kapsamlı yuvayı döndürüyoruz ( this.$scopedSlots['SLOTNAME'](...)
aracılığıyla) ve ilgili verileri yuva kapsamına geçiriyoruz. Bir şablon kullanmadığınızda, JavaScript'i script
etiketinden çıkarıp bir .js
dosyasına .vue
dosya uzantısını kullanmayı atlayabilirsiniz. Bu, bu Vue dosyalarını derlerken size çok hafif bir performans artışı sağlamalıdır.
Bu örnek, bazı potansiyel tuzakları kapsadıkları için yukarıdaki örneği kullanmamanızı tavsiye edeceğim, vue-promised'in soyulmuş ve hafifçe değiştirilmiş bir versiyonudur. Orada da renderless bileşenlerin birçok harika örneği var. Baleada, bunun gibi kullanışlı işlevsellik sağlayan renderless bileşenlerle dolu bir kütüphanedir. Ayrıca, ekranda görünenlere göre liste öğesinin oluşturulmasını kontrol etmek için vue-virtual-scroller veya DOM'un tamamen farklı bölümlerine içeriği “ışınlamak” için PortalVue vardır.
Ben yokum
Vue'nun slotları, bileşen tabanlı geliştirmeyi tamamen yeni bir seviyeye taşıyor ve slotların kullanılabileceği birçok harika yol göstermiş olsam da, orada sayısız daha fazlası var. Ne harika bir fikir düşünebilirsiniz? Sence slotlar hangi yollarla yükseltme alabilir? Varsa, fikirlerinizi Vue ekibine getirdiğinizden emin olun. Tanrı korusun ve mutlu kodlama.