Emit adalah cara child mengirim kabar ke parent: "barusan ada yang terjadi di sini". Props turun dari parent ke child, event naik dari child ke parent. Bayangin pembeli di depan kartu menu Warung Bu Sri yang menekan bel "Tambah". Kartu menu gak memegang keranjang, jadi dia gak bisa menambah pesanan sendiri. Dia cuma membunyikan bel sambil bilang "Nasi Goreng, 15000". Kasir yang mendengar bel itu yang mencatat ke keranjang. Kartu menu itu child, belnya event, dan kasir parent-nya.
🧪 Tebakan cepat:
MenuCardmemanggilemit("tambah")waktu tombolnya diklik, tapiApp.vuememasang<MenuCard />tanpa@tambah. Waktu tombolnya diklik: error merah, warning, atau gak terjadi apa-apa? Tebak dulu, jawabannya ada di bagian "Parent mendengarkan dengan @".
Child mengirim event dengan emit
<!-- MenuCard.vue -->
<script setup>
const props = defineProps({ nama: String, harga: Number })
const emit = defineEmits(["tambah"])
function klikTambah() {
emit("tambah", props.nama, props.harga)
}
</script>
<template>
<div class="kartu">
<h3>{{ nama }}</h3>
<p>Rp{{ harga }}</p>
<button @click="klikTambah">Tambah</button>
</div>
</template>
defineEmits(["tambah"])menyatakan event apa saja yang boleh dikirim komponen ini. SepertidefineProps, dia compiler macro, jadi gak perlu di-import.defineEmitsmengembalikan fungsi pengirimnya. Nama variabelnya bebas;emitcuma kebiasaan.emit("tambah", props.nama, props.harga): argumen pertama nama event, sisanya payload, yaitu data yang ikut dikirim.- Di template, kamu juga bisa memanggil
$emitlangsung:<button @click="$emit('tambah', nama, harga)">.
Parent mendengarkan dengan @
<!-- App.vue -->
<script setup>
import { ref } from "vue"
import MenuCard from "./MenuCard.vue"
const jumlah = ref(0)
const total = ref(0)
function tambahKeKeranjang(nama, harga) {
jumlah.value++
total.value += harga
console.log("Masuk keranjang:", nama)
}
</script>
<template>
<MenuCard nama="Nasi Goreng" :harga="15000" @tambah="tambahKeKeranjang" />
<MenuCard nama="Es Teh" :harga="5000" @tambah="tambahKeKeranjang" />
<p>Keranjang: {{ jumlah }} item, Rp{{ total }}</p>
</template>
| Klik | Payload yang sampai | jumlah |
total |
Console |
|---|---|---|---|---|
| awal | 0 | 0 | ||
| Tambah di Nasi Goreng | "Nasi Goreng", 15000 |
1 | 15000 | Masuk keranjang: Nasi Goreng |
| Tambah di Nasi Goreng | "Nasi Goreng", 15000 |
2 | 30000 | Masuk keranjang: Nasi Goreng |
| Tambah di Es Teh | "Es Teh", 5000 |
3 | 35000 | Masuk keranjang: Es Teh |
@tambah="tambahKeKeranjang"adalah singkatanv-on:tambah. Cara parent menangkap eventupdatepun sama:@update="...".- Payload sampai sebagai argumen handler, urutannya sama dengan urutan di
emit. - Handler pendek boleh ditulis langsung:
@tambah="jumlah++". Payload pertama juga bisa dibaca sebagai$event. - Kalau parent gak memasang
@tambah, gak ada yang mendengar. Event-nya hilang begitu saja, tanpa error dan tanpa warning. Itu jawaban tebakannya.
Props turun, event naik
Di pelajaran props, child gak boleh mengubah stok. Emit adalah jalan keluarnya: child melapor, parent yang mengubah.
<!-- MenuCard.vue, di template -->
<small>Stok: {{ stok }}</small>
<button @click="$emit('beli')">Beli 1</button>
<!-- App.vue, di template -->
<MenuCard nama="Mie Ayam" :stok="stok" @beli="stok--" />
Klik Beli 1: child mengirim beli, parent mengurangi ref stok miliknya, lalu angka baru turun lagi ke child lewat :stok. Datanya tetap punya satu pemilik.
📌 Nama event dan validasi
- Nama event ditulis camelCase di
emit("tambahKeranjang")dan didengarkan kebab-case di parent:@tambah-keranjang. Keduanya cocok. - Bentuk objek bisa memeriksa payload:
defineEmits({ tambah: (harga) => harga > 0 }). Kalau fungsinya mengembalikanfalse, build development menulis[Vue warn]: Invalid event arguments: event validation failed for event "tambah".Event-nya tetap terkirim; ini cuma peringatan. - Nyatakan semua event yang kamu kirim. Child yang root-nya
<button>dan mengirimclicktanpa menyatakannya membuat handler@clickdi parent jalan dua kali: sekali dari emit, sekali dari klik asli yang diteruskan ke root-nya. Denganemits: ["click"], cuma sekali.
File .vue dan editor latihan
Di editor, child ditulis sebagai objek dengan opsi emits, dan dipasang dengan <component :is>:
const jumlah = ref(0)
const total = ref(0)
function tambahKeKeranjang(nama, harga) {
jumlah.value++
total.value += harga
console.log("Masuk keranjang:", nama)
}
const MenuCard = {
props: { nama: String, harga: Number },
emits: ["tambah"],
template: `
<div class="kartu">
<h3>{{ nama }}</h3>
<p>Rp{{ harga }}</p>
<button @click="$emit('tambah', nama, harga)">Tambah</button>
</div>
`,
}
---template---
<div>
<component :is="MenuCard" nama="Nasi Goreng" :harga="15000" @tambah="tambahKeKeranjang" />
<component :is="MenuCard" nama="Es Teh" :harga="5000" @tambah="tambahKeKeranjang" />
<p>Keranjang: {{ jumlah }} item, Rp{{ total }}</p>
</div>
| Di file .vue | Di editor latihan |
|---|---|
const emit = defineEmits(["tambah"]) |
opsi emits: ["tambah"] |
emit("tambah", ...) di script |
$emit('tambah', ...) di template child, atau emit dari setup(props, { emit }) |
<MenuCard @tambah="..." /> |
<component :is="MenuCard" @tambah="..." /> |
- Komponen utama di editor gak punya parent, jadi
defineEmitsdi bagian script-nya gak mengirim ke mana-mana. Emit selalu ditulis di child. - Latihan pelajaran ini memakai bentuk itu: child objek dengan
emits,$emitdi template-nya, dan@tambah(atau@kurang) di<component>. Pemeriksanya membaca angka di halaman sebelum dan sesudah tombolnya diklik, dan juga ketiga bagian itu di kodemu.
⚠️ Jebakan yang sering ketemu
- Child mengubah data parent sendiri. Child cuma mengirim event; yang mengubah
countataustokadalah fungsi di parent. - Lupa memasang listener.
emit("tambah")tanpa@tambahdi parent gak melakukan apa-apa, tanpa pesan apa pun. - Fungsi handler yang menimpa, bukan menambah.
count.value = 1lolos klik pertama tapi gagal di klik kedua. Latihannya mengklik dua kali dan menunggu2. Tuliscount.value++. - Angka awal salah. Latihan kedua mulai dari
5: sebelum diklik5, setelah -1 jadi4, setelah +1 jadi6.
TL;DR: Emit mengirim kabar dari child ke parent. Child menyatakan event-nya dengan const emit = defineEmits(["tambah"]) lalu memanggil emit("tambah", nama, harga); argumen sesudah nama event adalah payload. Parent mendengarkan dengan @tambah="handler", dan payload sampai sebagai argumen handler. Event tanpa listener hilang tanpa error. Props turun, event naik: child melapor, parent yang mengubah datanya. Di editor latihan, child ditulis sebagai objek dengan emits: ["tambah"] dan $emit(...) di template-nya, lalu dipasang dengan <component :is>.