Vue · Bab 3
Komponen
Memecah antarmuka menjadi komponen dengan defineComponent, mengirim data lewat props tervalidasi, mengabarkan kejadian lewat emit, menyisipkan isi lewat slot, dan menghindari tiga jebakan template in-DOM.
Aplikasi di bab 2 masih satu template besar. Begitu bagian yang sama muncul di banyak tempat, misalnya kartu buku di daftar koleksi dan di halaman detail, template besar menjadi sulit dirawat. Komponen memecah antarmuka menjadi potongan kecil yang punya template, data, dan perilakunya sendiri, lalu bisa dipakai berulang seperti tag HTML buatanmu.
3.1 Mengapa Komponen?
Tanpa komponen, markup kartu buku disalin ke setiap tempat yang membutuhkannya. Mengubah tampilannya berarti mengubah semua salinan. Dengan komponen, kartu ditulis sekali sebagai <kartu-buku> dan setiap pemakaian hanya mengirim data yang berbeda.
Komponen dibuat dengan defineComponent({...}). Isinya sama dengan objek yang kamu berikan ke createApp di bab 1: ada setup(), ditambah template, props, dan emits. Komponen didaftarkan di bagian components milik induknya.
3.2 Template dari <template id>
Template komponen ditulis di dalam <template id="..."> di HTML, lalu dirujuk dengan template: '#id'. Isi <template> tidak ditampilkan browser; Vue mengambilnya sebagai template komponen.
🧪 Coba ubah: tambah kartu ketiga untuk “Negeri 5 Menara” karya Ahmad Fuadi.
Belum ada output.
Mengapa tidak menulis template sebagai string (template: '<li>...</li>')? Di string, editor tidak bisa mewarnai HTML-nya, tanda kutip mudah bentrok, dan komentar atau catatan yang tertulis di dalamnya ikut tampil sebagai teks. <template id> adalah HTML biasa yang mudah dibaca.
Nama komponen ditulis PascalCase di skrip (KartuBuku) dan kebab-case di template (<kartu-buku>). Vue menghubungkan keduanya otomatis.
3.3 Props dan Validasinya
Props adalah data yang dikirim induk ke komponen, ditulis seperti atribut. Atribut biasa mengirim teks (judul="Bumi Manusia"); atribut dengan : mengirim nilai JavaScript (:tersedia="2" mengirim angka 2, :tersedia="b.tersedia" mengirim isi variabel).
Daftar nama (props: ['judul']) cukup untuk mencoba, tetapi komponen yang dipakai orang lain sebaiknya menyatakan tipe dan aturannya:
🧪 Coba ubah: jalankan dan buka tab Console. Kartu ketiga mengirim teks "dua" untuk prop bertipe Number, sehingga Vue menulis peringatan [Vue warn]: Invalid prop. Perbaiki menjadi :tersedia="1".
Belum ada output.
Kartu kedua tidak mengirim tersedia, jadi nilai default: 0 dipakai. Peringatan validasi hanya muncul selama pengembangan; ia membantu menemukan salah kirim lebih awal, bukan mengamankan data.
3.4 Emits: Mengabarkan Kejadian ke Induk
Data mengalir turun lewat props. Kabar mengalir naik lewat event: komponen memanggil emit('nama-event', data), induk mendengarkan dengan @nama-event="...". Di induk, data yang dikirim tersedia sebagai $event atau sebagai argumen fungsi.
Deklarasikan event yang dikirim komponen di emits. Selain menjadi dokumentasi, deklarasi itu mencegah pendengar dari induk jatuh menjadi atribut pada elemen akar komponen.
🧪 Coba ubah: kirim objek berisi judul dan tanggal, misalnya emit('pinjam', { judul: props.judul, tanggal: '12 Mei' }), lalu sesuaikan catat.
Belum ada output.
setup(props, { emit }) menerima props sebagai argumen pertama. Baca prop lewat props.judul; jangan memecahnya (const { judul } = props) karena hasilnya tidak reaktif lagi.
3.5 Slot: Isi dari Induk
Kadang induk ingin menaruh isi bebas di dalam komponen, misalnya label rak atau tombol yang berbeda di setiap halaman. Komponen menyediakan tempatnya dengan <slot>; apa pun yang ditulis di antara tag pembuka dan penutup komponen masuk ke sana. Isi di dalam <slot>...</slot> sendiri menjadi isi bawaan bila induk tidak mengirim apa-apa.
🧪 Coba ubah: kirim tombol <button type="button">Pesan</button> sebagai isi slot kartu kedua.
Belum ada output.
3.6 Tiga Jebakan Template In-DOM
Template di materi dan lab ditulis langsung di HTML halaman, jadi browser membacanya lebih dulu, baru Vue. Parser HTML punya aturan sendiri yang bisa merusak template sebelum Vue melihatnya.
🧪 Coba ubah: ganti <tr is="vue:baris-buku" ...></tr> menjadi <baris-buku ...></baris-buku> dan lihat baris tabelnya hilang atau pindah ke luar tabel. Lalu kembalikan, dan ganti :jatuh-tempo menjadi :jatuhTempo untuk melihat jebakan pertama.
Belum ada output.
Ketiga jebakan ini hanya ada pada template in-DOM. Di proyek dengan build tools (bab 10), template ditulis di berkas .vue dan dikompilasi sebelum sampai ke browser, sehingga self-closing dan PascalCase boleh dipakai.
3.7 Aliran Data Satu Arah
Komponen tidak boleh mengubah prop-nya sendiri. Prop milik induk; bila anak mengubahnya, nilai itu akan tertimpa saat induk merender ulang, dan Vue menulis peringatan di konsol. Bila anak perlu mengubah data, ia meminta induk lewat event, dan induk yang mengubah state-nya.
| Arah | Cara | Contoh |
|---|---|---|
| induk → anak | props | :judul="b.judul" |
| anak → induk | emit + pendengar | emit('pinjam', id) dan @pinjam="catat" |
| induk → isi anak | slot | <kartu-buku>Rak A3</kartu-buku> |
Bila komponen butuh nilai awal dari prop lalu mengubahnya sendiri (misalnya kotak isian yang diawali judul lama), salin dulu ke state lokal: const judulBaru = ref(props.judul).
Rangkuman
defineComponent({ template: '#id', props, emits, setup(props, { emit }) }), didaftarkan dicomponentsinduk.- Nama komponen PascalCase di skrip, kebab-case di template.
- Props dengan
type,required, dandefault; peringatan[Vue warn]menandai kiriman yang salah. - Kabar naik lewat
emit(...)dan pendengar@event; isi bebas masuk lewat<slot>. - Template in-DOM: atribut kebab-case, tanpa self-closing, dan
is="vue:..."di dalam tabel.
Berikutnya: Lab bab 3 memecah etalase Smart Retail menjadi komponen kartu dengan props, slot, dan emit.