Panduan Lengkap tentang Slot Vue
Panduan Lengkap tentang Slot Vue
Slot adalah salah satu fitur Vue yang paling kuat untuk membangun komponen yang fleksibel dan dapat digunakan kembali. Slot memungkinkan induk memutuskan apa yang akan dirender sementara anak mengontrol di mana dan bagaimana tampilannya. Baik Anda membungkus konten dalam kartu, membangun tata letak, atau menyesuaikan setiap baris daftar, slot adalah alat yang tepat. Panduan ini membahas dasar-dasar, teknik lanjutan, dan banyak skenario praktis yang akan Anda gunakan setiap hari.
Dasar-Dasar Slot Vue
Apa Itu Slot Vue?
Sebuah slot adalah tempat penampung di komponen anak yang diisi dengan konten yang diberikan dari induk. Anak mendefinisikan satu atau lebih outlet <slot>; induk menyediakan kontennya. Ini menjaga struktur dan perilaku anak (misalnya gaya, logika) sambil memberikan kendali penuh kepada induk atas konten HTML yang sebenarnya.
Bayangkan sebuah tombol: anak mungkin menangani tipe, status dinonaktifkan, dan penanganan klik, tetapi label (mungkin beserta ikon) berasal dari induk. Slot adalah cara Anda melewatkan label (atau fragmen templat apa pun) ke dalam.
Slot Default untuk Slot Vue
Bentuk paling sederhana adalah satu slot tanpa nama—slot default. Apa pun yang Anda letakkan di antara tag pembuka dan penutup anak akan dimasukkan ke dalam slot tersebut.
Komponen anak (ButtonSubmit):
<script setup lang="ts"> // Menangani logika submit, styling, dll. </script> <template> <button type="submit" class="btn btn-primary"> <slot></slot> </button> </template>Penggunaan induk:
<template> <ButtonSubmit><IconSave />Simpan perubahan</ButtonSubmit> </template>Konteng Fallback untuk Slot Vue
Anda dapat meletakkan konten di dalam <slot> di anak. Konten tersebut digunakan hanya jika induk tidak menyediakan apa pun untuk slot tersebut. Dengan kata lain, ini adalah default slot.
<!-- ButtonSubmit --> <template> <button type="submit" class="btn btn-primary"> <slot>Submit</slot> </button> </template>Jika induk menggunakan <ButtonSubmit></ButtonSubmit> tanpa konten, "Submit" akan ditampilkan. Jika induk menggunakan <ButtonSubmit>Simpan</ButtonSubmit>, "Simpan" akan ditampilkan.
Pola Slot dan Props Templat
Menentukan default slot dengan nilai prop adalah pola desain komponen Vue yang umum untuk meningkatkan API komponen secara progresif. Ini memungkinkan penggunaan cepat dengan prop tetapi tetap fleksibel untuk kebutuhan yang lebih kompleks. Misalnya, Anda dapat mendefinisikan komponen tombol sederhana dengan prop label bersama dengan slot untuk label:
<!-- Button --> <script setup lang="ts"> defineProps<{ label?: string }>(); </script> <template> <button type="submit" class="btn btn-primary"> <slot>{{ label }}</slot> </button> </template>Ini persis apa yang dilakukan oleh pustaka UI populer seperti Nuxt UI dengan komponen mereka.
Slot Bernama
Ketika sebuah komponen membutuhkan beberapa area konten yang berbeda, gunakan slot bernama. Anak memberikan setiap <slot> sebuah name; induk menargetkannya dengan v-slot (atau singkatan #) pada <template>.
Komponen anak (Card):
<script setup lang="ts"> defineProps<{ title?: string }>(); </script> <template> <article class="card"> <header class="card-header"> <slot name="header"></slot> </header> <div class="card-body"> <slot></slot> </div> <footer class="card-footer"> <slot name="footer"></slot> </footer> </article> </template>Slot tanpa nama adalah slot default. Induk mengisi setiap slot berdasarkan nama:
<template> <Card> <template #header> <h2>Pengaturan akun</h2> </template> <template #default> <p>Perbarui email dan kata sandi Anda.</p> </template> <template #footer> <button>Simpan</button> </template> </Card> </template>#headeradalah singkatan dariv-slot:header.#defaultmenargetkan slot default. Anda juga dapat meletakkan konten utama langsung di antara<Card>dan</Card>tanpa pembungkus; itu akan masuk ke slot default.
Jadi: satu slot default untuk konten utama, ditambah sebanyak mungkin slot bernama yang Anda butuhkan (header, footer, sidebar, dll.).
Teknik Slot Lanjutan Vue
Slot Lingkup (Props Slot)
Terkadang anak memiliki data yang diperlukan induk untuk merender slot (misalnya setiap item dalam daftar, atau status internal). Slot lingkup memungkinkan anak melewatkan props ke dalam slot; induk menerimanya dan menggunakannya dalam templat.
Anak mengekspos data dengan mengikat atribut pada <slot>:
<!-- Anak: MyComponent --> <script setup lang="ts"> import { ref } from "vue"; const greetingMessage = ref("Halo dari anak!"); </script> <template> <div> <slot :text="greetingMessage" :count="1"></slot> </div> </template>Induk menerima satu objek (misalnya slotProps) dan menggunakannya:
<!-- Induk: App --> <template> <MyComponent v-slot="slotProps"> {{ slotProps }} — jumlah: {{ slotProps }} </MyComponent> </template>Jika hanya ada slot default dan Anda membutuhkan props, Anda dapat menggunakan v-slot langsung pada komponen. Jika Anda memiliki beberapa slot atau perlu menamai slot default secara eksplisit, gunakan <template #default="slotProps">.
Destrukturisasi Props Slot
Anda dapat mendestrukturisasi props langsung di v-slot untuk kode templat yang lebih bersih:
<template> <MyComponent v-slot="{ text, count }"> {{ text }} — jumlah: {{ count }} </MyComponent> </template>Anda juga dapat mengganti nama props seperti ini: v-slot="{ text: greeting, count }". Ini berguna untuk:
- Menghindari benturan nama
- Memperjelas arti props dalam konteks induk
Slot Bernama dengan Lingkup
Slot bernama juga dapat melewatkan props. Anak memberikan slot sebuah nama dan mengikat data; induk menggunakan nama yang sama dan menerima props:
Anak:
<template> <div> <slot name="header" :title="pageTitle"></slot> <slot :items="items"></slot> <slot name="footer" :year="currentYear"></slot> </div> </template>Induk:
<template> <MyComponent> <template #header="{ title }"> <h1>{{ title }}</h1> </template> <template #default="{ items }"> <ul> <li v-for="item in items" :key="item">{{ item }}</li> </ul> </template> <template #footer="{ year }"> <p>© {{ year }}</p> </template> </MyComponent> </template>Jadi: slot bernama mendefinisikan di mana konten ditempatkan; slot lingkup mendefinisikan data apa yang didapat induk saat merender konten tersebut.
Mencampur Slot Default Eksplisit dan Slot Bernama dengan Lingkup
Jika Anda memiliki slot default dengan lingkup dan slot bernama lainnya, gunakan slot default eksplisit agar lingkup jelas:
<MyComponent> <template #default="{ message }"> <p>{{ message }}</p> </template> <template #footer> <p>Berikut info kontak</p> </template> </MyComponent>Jika Anda meletakkan konten di antara <MyComponent> dan </MyComponent> tanpa <template #default>, konten tersebut tidak memiliki akses ke props slot default. Selalu gunakan <template #default="..."> jika Anda membutuhkan props tersebut.
Melewatkan Nama Slot Dinamis
Nama slot dapat dilewatkan secara dinamis menggunakan sintaks tanda kurung yang sama seperti direktif lainnya:
<template> <BaseLayout> <template v-slot:[dynamicSlotName]> Konten untuk {{ dynamicSlotName }} </template> <!-- Singkatan --> <template #[dynamicSlotName]> ... </template> </BaseLayout> </template>Mendefinisikan Nama Slot Dinamis
Selain melewatkan nama slot secara dinamis, Anda bahkan dapat mendefinisikannya secara dinamis. Saya menemukan ini sangat berguna untuk menyesuaikan sel tabel untuk kolom tertentu.
<template> <table> <tbody> <tr v-for="item in items" :key="item"> <td v-for="column in columns" :key="column"> <slot :name="`item-${column}`" :item="item"> {{ item[column] }} </slot> </td> </tr> </tbody> </table> </template>Slot Bersyarat
Anda dapat menggunakan v-if bersama objek $slots untuk merender elemen HTML lain secara bersyarat berdasarkan apakah induk menyediakannya atau tidak.
<template> <div v-if="$slots"> <slot name="header"> <h1>Header default</h1> </slot> </div> </template>Skenario Praktis untuk Slot Vue
1. Komponen Kartu (header, body, footer)
Kartu yang dapat digunakan kembali adalah kasus penggunaan yang sempurna untuk slot. Kartu dapat mendefinisikan struktur dan gaya tetap tetapi membiarkan konten sepenuhnya dikendalikan oleh induk.
AppCard:
<script setup lang="ts"></script> <template> <article class="rounded-lg border bg-card p-4 shadow-sm"> <header v-if="$slots" class="mb-3 border-b pb-2"> <slot name="header"></slot> </header> <div class="card-body"> <slot></slot> </div> <footer v-if="$slots" class="mt-3 border-t pt-2"> <slot name="footer"></slot> </footer> </article> </template>Menggunakan v-if="$slots" (dan $slots) menjaga header/footer DOM hanya ketika induk benar-benar menyediakan slot tersebut. Penggunaan induk:
<AppCard> <template #header> <h3>Profil</h3> </template> <p>Bio dan preferensi Anda.</p> <template #footer> <button>Edit profil</button> </template> </AppCard>2. Tata Letak Halaman (header, sidebar, main, footer)
Komponen tata letak yang menyediakan area bernama sehingga induk dapat mengisinya per halaman.
AppLayout:
<script setup lang="ts"></script> <template> <div class="app-layout"> <header v-if="$slots" class="app-header"> <slot name="header"></slot> </header> <div class="app-body"> <aside v-if="$slotsr" class="app-sidebar"> <slot name="sidebar"></slot> </aside> <main class="app-main"> <slot></slot> </main> </div> <footer v-if="$slots" class="app-footer"> <slot name="footer"></slot> </footer> </div> </template>Induk (misalnya halaman):
<template> <AppLayout> <template #header> <AppNav /> </template> <template #sidebar> <AppSidebar /> </template> <h1>Dasbor</h1> <p>Konten halaman utama di sini.</p> <template #footer> <p>© 2025 Aplikasi Saya</p> </template> </AppLayout> </template>3. Tabel Data / Daftar dengan Baris yang Dapat Disesuaikan
Anak mengambil atau menyimpan daftar dan menangani tata letak; induk memutuskan bagaimana setiap baris terlihat menggunakan slot lingkup.
DataTable:
<script setup> defineProps({ items: { type: Array, required: true, }, columns: { type: Array, required: true, }, }); </script> <template> <table class="table"> <thead> <tr> <th v-for="column in columns" :key="column">{{ column }}</th> </tr> </thead> <tbody> <tr v-for="(item, index) in items" :key="JSONify(item)"> <slot :name="`row-${index}`" :item="item"> <td v-for="column in columns" :key="column"> <slot :name="`item-${column}`" :item="item"> {{ item[column] }} </slot> </td> </slot> </tr> </tbody> </table> </template>Induk:
<script setup lang="ts"> import MyTable from "./components/MyTable"; import { ref } from "vue"; const tableData = ref({ columns: [ { key: "name", label: "Nama" }, { key: "age", label: "Usia" }, ], items: [ { name: "John", age: 20 }, { name: "Jane", age: 24 }, { name: "Susan", age: 16 }, { name: "Chris", age: 55 }, { name: "Dan", age: 40 }, ], }); </script> <template> <MyTable :items="tableData" :columns="tableDatas"> <template #row-0> <td>halo</td> <td>halo</td> </template> <template #item-name="{ item }"> <strong>{{ item }}</strong> </template> </MyTable> </template>MyTable yang sama dapat digunakan kembali untuk produk, pesanan, dll.—hanya konten slot yang berubah.
4. Pembungkus Formulir dengan Kolom dan Aksi
Kontainer formulir yang menyediakan tata letak dan penanganan status submit, dengan slot untuk kolom dan aksi (tombol).
FormSection:
<script setup lang="ts"> defineProps<{ title?: string; submit: () => void }>(); const isSubmitting = ref(false); async function handleSubmit() { isSubmitting = true; await props(); isSubmitting = false; } </script> <template> <form class="form-section" @submitt="handleSubmit"> <fieldset> <legend v-if="title">{{ title }}</legend> <slot></slot> </fieldset> <div v-if="$slotss" class="form-actions" :class="{ 'pointer-events-none opacity-50': isSubmitting }"> <slot name="actions" :isSubmitting="isSubmitting"></slot> </div> </form> </template>Induk:
<template> <FormSection title="Alamat pengiriman" @submit="onSubmit"> <input v-model="forms" placeholder="Alamat" /> <input v-model="form" placeholder="Kota" /> <template #actions> <button type="button" @click="cancel">Batal</button> <button type="submit">Simpan</button> </template> </FormSection> </template>5. Tab dengan Nama Slot Dinamis
Tab dapat diimplementasikan dengan slot per tab; nama slot adalah id tab.
AppTabs:
<script setup lang="ts"> import { ref, computed } from "vue"; const props = defineProps<{ tabs: Array<{ id: string; label: string }>; }>(); const activeId = ref(props[0]?.id ?? ""); const activeSlot = computed(() => activeId); </script> <template> <div class="tabs"> <div class="tabs-header"> <button v-for="tab in tabs" :key="tab" :class="{ active: activeId === tab }" @click="activeId = tab"> {{ tab }} </button> </div> <div class="tabs-panels"> <template v-for="tab in tabs" :key="tab"> <div v-show="activeId === tab"> <slot :name="tab"></slot> </div> </template> </div> </div> </template>Induk:
<template> <AppTabs :tabs="[ { id: 'overview', label: 'Ikhtisar' }, { id: 'settings', label: 'Pengaturan' }, ]"> <template #overview> <p>Konten ikhtisar...</p> </template> <template #settings> <p>Formulir pengaturan...</p> </template> </AppTabs> </template>Di sini nama slot berasal dari data (tab), yang cocok untuk nama slot dinamis.
Ringkasan Slot Vue
- Slot default: Satu
<slot>tanpa nama; konten induk di antara tag komponen masuk ke sana. Gunakan konten fallback di dalam<slot>untuk default. - Slot bernama: Gunakan
<slot name="...">dan<template #name>(atauv-slot:name) untuk menargetkan area tertentu (header, footer, sidebar, dll.). - Slot lingkup: Ikat atribut pada
<slot>; induk menerimanya dalamv-slot="props"atau#default="props"dan dapat mendestrukturisasinya. Gunakan untuk daftar, tabel, dan kasus di mana anak memiliki data tetapi induk mengontrol presentasi. - Nama slot dinamis: Gunakan
#[dynamicName]atauv-slot:[dynamicName]ketika nama slot dihitung atau dari konfigurasi (misalnya tata letak, tab). - Area bersyarat: Gunakan
v-if="$slots"sehingga Anda hanya merender elemen pembungkus ketika induk benar-benar menyediakan slot tersebut.
Setelah Anda nyaman dengan pola-pola ini, Anda dapat merancang komponen yang tetap generik dan dapat digunakan kembali sambil memberikan setiap layar kendali penuh atas konten dan tata letak.
Platform Lainnya
Berita Piala Dunia
Jika Anda memiliki pertanyaan, silakan kirim email ke [email protected]