Menggunakan Slot Di Vue.js
Diterbitkan: 2022-03-10Dengan rilis terbaru dari Vue 2.6, sintaks untuk menggunakan slot telah dibuat lebih ringkas. Perubahan pada slot ini membuat saya kembali tertarik untuk menemukan potensi kekuatan slot untuk menyediakan penggunaan kembali, fitur baru, dan keterbacaan yang lebih jelas untuk proyek berbasis Vue kami. Apa yang benar-benar mampu dilakukan oleh slot?
Jika Anda baru mengenal Vue atau belum melihat perubahan dari versi 2.6, baca terus. Mungkin sumber terbaik untuk belajar tentang slot adalah dokumentasi Vue sendiri, tetapi saya akan mencoba memberikan ringkasannya di sini.
Apa Itu Slot?
Slot adalah mekanisme untuk komponen Vue yang memungkinkan Anda menyusun komponen dengan cara selain hubungan orangtua-anak yang ketat. Slot memberi Anda jalan keluar untuk menempatkan konten di tempat baru atau membuat komponen lebih umum. Cara terbaik untuk memahaminya adalah dengan melihatnya beraksi. Mari kita mulai dengan contoh sederhana:
// frame.vue <template> <div class="frame"> <slot></slot> </div> </template>
Komponen ini memiliki pembungkus div
. Mari kita berpura-pura bahwa div
ada untuk membuat bingkai gaya di sekitar kontennya. Komponen ini dapat digunakan secara umum untuk membungkus bingkai di sekitar konten apa pun yang Anda inginkan. Mari kita lihat seperti apa cara menggunakannya. Komponen frame
di sini mengacu pada komponen yang baru saja kita buat di atas.
// app.vue <template> <frame><img src="an-image.jpg"></frame> </template>
Konten yang berada di antara tag frame
pembuka dan penutup akan dimasukkan ke dalam komponen frame
tempat slot
berada, menggantikan tag slot
. Ini adalah cara paling dasar untuk melakukannya. Anda juga dapat menentukan konten default untuk dimasukkan ke dalam slot hanya dengan mengisinya:
// frame.vue <template> <div class="frame"> <slot>This is the default content if nothing gets specified to go here</slot> </div> </template>
Jadi sekarang jika kita menggunakannya seperti ini sebagai gantinya:
// app.vue <template> <frame /> </template>
Teks default "Ini adalah konten default jika tidak ada yang ditentukan untuk pergi di sini" akan muncul, tetapi jika kita menggunakannya seperti yang kita lakukan sebelumnya, teks default akan ditimpa oleh tag img
.
Beberapa/Bernama Slot
Anda dapat menambahkan beberapa slot ke komponen, tetapi jika Anda melakukannya, semua kecuali satu dari mereka harus memiliki nama. Jika ada satu tanpa nama, itu adalah slot default. Inilah cara Anda membuat banyak slot:
// 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>
Kami menyimpan slot default yang sama, tetapi kali ini kami menambahkan slot bernama header
tempat Anda dapat memasukkan judul. Anda menggunakannya seperti ini:
// 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>
Sama seperti sebelumnya, jika kita ingin menambahkan konten ke slot default, masukkan saja langsung ke dalam komponen titled-frame
. Untuk menambahkan konten ke slot bernama, kami perlu membungkus kode dalam tag template
dengan direktif v-slot
. Anda menambahkan titik dua ( :
) setelah v-slot
dan kemudian tulis nama slot yang Anda inginkan untuk dilewati konten. Perhatikan bahwa v-slot
baru untuk Vue 2.6, jadi jika Anda menggunakan versi yang lebih lama, Anda harus membaca dokumen tentang sintaks slot yang tidak digunakan lagi.
Slot Tercakup
Satu hal lagi yang perlu Anda ketahui adalah bahwa slot dapat meneruskan data/fungsi ke turunannya. Untuk mendemonstrasikan ini, kita memerlukan komponen contoh yang sama sekali berbeda dengan slot, yang bahkan lebih dibuat-buat daripada yang sebelumnya: mari kita salin contoh dari dokumen dengan membuat komponen yang memasok data tentang pengguna saat ini ke slotnya:
// current-user.vue <template> <span> <slot v-bind:user="user"> {{ user.lastName }} </slot> </span> </template> <script> export default { data () { return { user: ... } } } </script>
Komponen ini memiliki properti yang disebut user
dengan detail tentang pengguna. Secara default, komponen menunjukkan nama belakang pengguna, tetapi perhatikan bahwa ia menggunakan v-bind
untuk mengikat data pengguna ke slot. Dengan itu, kita dapat menggunakan komponen ini untuk memberikan data pengguna kepada turunannya:
// app.vue <template> <current-user> <template v-slot:default="slotProps">{{ slotProps.user.firstName }}</template> </current-user> </template>
Untuk mendapatkan akses ke data yang dikirimkan ke slot, kami menentukan nama variabel lingkup dengan nilai direktif v-slot
.
Ada beberapa catatan yang perlu diperhatikan di sini:
- Kami menentukan nama
default
, meskipun kami tidak perlu untuk slot default. Sebagai gantinya kita bisa menggunakanv-slot="slotProps"
. - Anda tidak perlu menggunakan
slotProps
sebagai namanya. Anda dapat menyebutnya apa pun yang Anda inginkan. - Jika Anda hanya menggunakan slot default, Anda dapat melewati tag
template
bagian dalam itu dan menempatkan direktifv-slot
langsung ke tagcurrent-user
. - Anda dapat menggunakan perusakan objek untuk membuat referensi langsung ke data slot tercakup daripada menggunakan nama variabel tunggal. Dengan kata lain, Anda dapat menggunakan
v-slot="{user}"
sebagai gantiv-slot="slotProps"
dan kemudian Anda dapat menggunakanuser
secara langsung alih-alihslotProps.user
.
Dengan memperhatikan catatan tersebut, contoh di atas dapat ditulis ulang seperti ini:
// app.vue <template> <current-user v-slot="{user}"> {{ user.firstName }} </current-user> </template>
Beberapa hal lagi yang perlu diingat:
- Anda dapat mengikat lebih dari satu nilai dengan arahan
v-bind
. Jadi dalam contoh, saya bisa melakukan lebih dari sekadaruser
. - Anda juga dapat meneruskan fungsi ke slot yang dicakup. Banyak perpustakaan menggunakan ini untuk menyediakan komponen fungsional yang dapat digunakan kembali seperti yang akan Anda lihat nanti.
-
v-slot
memiliki alias#
. Jadi alih-alih menulisv-slot:header="data"
, Anda dapat menulis#header="data"
. Anda juga dapat menentukan#header
alih-alihv-slot:header
saat Anda tidak menggunakan slot tercakup. Untuk slot default, Anda harus menentukan namadefault
saat menggunakan alias. Dengan kata lain, Anda harus menulis#default="data"
alih-alih#="data"
.
Ada beberapa poin kecil lainnya yang dapat Anda pelajari dari dokumen, tetapi itu seharusnya cukup untuk membantu Anda memahami apa yang sedang kita bicarakan di sisa artikel ini.
Apa yang Dapat Anda Lakukan Dengan Slot?
Slot tidak dibuat untuk satu tujuan, atau setidaknya jika memang demikian, slot telah berkembang jauh melampaui niat awal untuk menjadi alat pembangkit tenaga listrik untuk melakukan banyak hal berbeda.
Pola yang dapat digunakan kembali
Komponen selalu dirancang untuk dapat digunakan kembali, tetapi beberapa pola tidak praktis untuk diterapkan dengan satu komponen "normal" karena jumlah props
yang Anda perlukan untuk menyesuaikannya bisa berlebihan atau Anda perlu melewati sebagian besar konten dan komponen potensial lainnya melalui props
. Slot dapat digunakan untuk mencakup bagian "luar" dari pola dan memungkinkan HTML dan/atau komponen lain ditempatkan di dalamnya untuk menyesuaikan bagian "dalam", memungkinkan komponen dengan slot untuk menentukan pola dan komponen yang disuntikkan ke dalam slot menjadi unik.
Untuk contoh pertama kita, mari kita mulai dengan sesuatu yang sederhana: sebuah tombol. Bayangkan Anda dan tim Anda menggunakan Bootstrap*. Dengan Bootstrap, tombol Anda sering diikat dengan kelas `btn` dasar dan kelas yang menentukan warna, seperti `btn-primary`. Anda juga dapat menambahkan kelas ukuran, seperti `btn-lg`.
* Saya tidak mendorong atau mencegah Anda melakukan ini, saya hanya membutuhkan sesuatu untuk contoh saya dan itu cukup terkenal.
Sekarang mari kita asumsikan, demi kesederhanaan bahwa aplikasi/situs Anda selalu menggunakan btn-primary
dan btn-lg
. Anda tidak ingin selalu harus menulis ketiga kelas pada tombol Anda, atau mungkin Anda tidak mempercayai pemula untuk mengingat melakukan ketiganya. Dalam hal ini, Anda dapat membuat komponen yang secara otomatis memiliki ketiga kelas tersebut, tetapi bagaimana Anda mengizinkan penyesuaian konten? Sebuah prop
tidak praktis karena tag button
diperbolehkan untuk memiliki semua jenis HTML di dalamnya, jadi kita harus menggunakan slot.
<!-- my-button.vue --> <template> <button class="btn btn-primary btn-lg"> <slot>Click Me!</slot> </button> </template>
Sekarang kami dapat menggunakannya di mana saja dengan konten apa pun yang Anda inginkan:
<!-- 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>
Tentu saja, Anda dapat menggunakan sesuatu yang jauh lebih besar daripada tombol. Tetap menggunakan Bootstrap, mari kita lihat modal, atau setidaknya bagian HTML; Saya tidak akan masuk ke fungsionalitas ... belum.
<!-- 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>
Sekarang, mari kita gunakan ini:
<!-- 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>
Jenis kasus penggunaan slot di atas jelas sangat berguna, tetapi dapat melakukan lebih banyak lagi.
Menggunakan kembali Fungsionalitas
Komponen Vue tidak semuanya tentang HTML dan CSS. Mereka dibuat dengan JavaScript, jadi mereka juga tentang fungsionalitas. Slot dapat berguna untuk membuat fungsionalitas sekali dan menggunakannya di banyak tempat. Mari kembali ke contoh modal kita dan tambahkan fungsi yang menutup modal:
<!-- 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>
Sekarang ketika Anda menggunakan komponen ini, Anda dapat menambahkan tombol ke footer yang dapat menutup modal. Biasanya, dalam kasus modal Bootstrap, Anda bisa menambahkan data-dismiss="modal"
ke tombol, tetapi kami ingin menyembunyikan hal-hal khusus Bootstrap dari komponen yang akan dimasukkan ke dalam komponen modal ini. Jadi kami memberikan mereka fungsi yang dapat mereka panggil dan mereka tidak lebih bijaksana tentang keterlibatan 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>
Komponen Tanpa Render
Dan akhirnya, Anda dapat mengambil apa yang Anda ketahui tentang menggunakan slot untuk menyebarkan fungsionalitas yang dapat digunakan kembali dan menghapus hampir semua HTML dan hanya menggunakan slot. Pada dasarnya itulah komponen tanpa render: komponen yang hanya menyediakan fungsionalitas tanpa HTML apa pun.
Membuat komponen yang benar-benar tanpa render bisa menjadi sedikit rumit karena Anda harus menulis fungsi render
daripada menggunakan template untuk menghilangkan kebutuhan akan elemen root, tetapi mungkin tidak selalu diperlukan. Mari kita lihat contoh sederhana yang memungkinkan kita menggunakan template terlebih dahulu:
<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>
Ini adalah contoh aneh dari komponen tanpa render karena tidak memiliki JavaScript di dalamnya. Itu sebagian besar karena kami baru saja membuat versi pra-konfigurasi yang dapat digunakan kembali dari fungsi renderless bawaan: transition
.
Yup, Vue memiliki komponen renderless bawaan. Contoh khusus ini diambil dari artikel tentang transisi yang dapat digunakan kembali oleh Cristi Jora dan menunjukkan cara sederhana untuk membuat komponen tanpa render yang dapat menstandardisasi transisi yang digunakan di seluruh aplikasi Anda. Artikel Cristi membahas lebih dalam dan menunjukkan beberapa variasi lanjutan dari transisi yang dapat digunakan kembali, jadi saya sarankan untuk memeriksanya.
Untuk contoh kami yang lain, kami akan membuat komponen yang menangani pengalihan apa yang ditampilkan selama status Promise yang berbeda: tertunda, berhasil diselesaikan, dan gagal. Ini adalah pola yang umum dan meskipun tidak memerlukan banyak kode, ini dapat mengacaukan banyak komponen Anda jika logikanya tidak ditarik untuk dapat digunakan kembali.
<!-- 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>
Jadi apa yang terjadi di sini? Pertama, perhatikan bahwa kita menerima prop yang disebut promise
yaitu Promise
. Di bagian watch
kami melihat perubahan pada janji dan ketika itu berubah (atau segera pada pembuatan komponen berkat properti immediate
) kami menghapus status, dan memanggil then
dan catch
janji, memperbarui status ketika selesai dengan sukses atau gagal.
Kemudian, di template, kami menunjukkan slot yang berbeda berdasarkan status. Perhatikan bahwa kami gagal membuatnya benar-benar tanpa render karena kami membutuhkan elemen root untuk menggunakan template. Kami juga meneruskan data
dan error
ke cakupan slot yang relevan.
Dan inilah contoh penggunaannya:
<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> ...
Kami meneruskan somePromise
ke komponen tanpa render. Sementara kami menunggunya selesai, kami menampilkan "Sedang mengerjakannya ..." berkat slot yang pending
. Jika berhasil kita tampilkan “Resolved:” dan nilai resolusinya. Jika gagal kami menampilkan "Ditolak:" dan kesalahan yang menyebabkan penolakan. Sekarang kita tidak perlu lagi melacak keadaan janji dalam komponen ini karena bagian itu ditarik ke dalam komponennya sendiri yang dapat digunakan kembali.
Jadi, apa yang dapat kita lakukan tentang span
yang membungkus slot di promised.vue
? Untuk menghapusnya, kita harus menghapus bagian template
dan menambahkan fungsi render
ke komponen kita:
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']() }
Tidak ada sesuatu yang terlalu rumit terjadi di sini. Kami hanya menggunakan beberapa blok if
untuk menemukan status dan kemudian mengembalikan slot cakupan yang benar (melalui this.$scopedSlots['SLOTNAME'](...)
) dan meneruskan data yang relevan ke cakupan slot. Saat Anda tidak menggunakan template, Anda dapat melewati penggunaan ekstensi file .vue
dengan menarik JavaScript dari tag script
dan hanya memasukkannya ke dalam file .js
. Ini akan memberi Anda sedikit peningkatan kinerja saat mengkompilasi file Vue tersebut.
Contoh ini adalah versi vue-promised yang dilucuti dan sedikit diubah, yang saya sarankan untuk menggunakan contoh di atas karena mencakup beberapa potensi jebakan. Ada banyak contoh hebat lainnya dari komponen tanpa render di luar sana juga. Baleada adalah seluruh pustaka yang penuh dengan komponen tanpa render yang menyediakan fungsionalitas berguna seperti ini. Ada juga vue-virtual-scroller untuk mengontrol rendering item daftar berdasarkan apa yang terlihat di layar atau PortalVue untuk konten "teleportasi" ke bagian DOM yang sama sekali berbeda.
Saya keluar
Slot Vue membawa pengembangan berbasis komponen ke tingkat yang sama sekali baru, dan sementara saya telah menunjukkan banyak cara hebat slot dapat digunakan, ada banyak lagi di luar sana. Ide bagus apa yang bisa kamu pikirkan? Menurut Anda cara apa slot bisa mendapatkan peningkatan? Jika Anda memilikinya, pastikan untuk membawa ide Anda ke tim Vue. Tuhan memberkati dan senang coding.