Component event adalah kabar yang dikirim komponen anak ke induknya, misalnya "menu ini dipesan". Props mengalir turun, dari induk ke anak; event mengalir naik, dari anak ke induk. Bayangin bel di tiap meja Warung Bu Sri. Pembeli menekan bel sambil menyebut "Nasi Goreng". Bunyinya cuma terdengar di ruangan tempat bel itu dipasang. Kasir di ruang depan mendengarnya, tapi Bu Sri di dapur gak, kecuali kasirnya meneruskan pesanan itu. Kalau ruangannya kosong, belnya tetap berbunyi, gak ada yang datang, dan gak ada yang protes.
🧪 Tebakan cepat: App menulis
<DaftarMenu on:pesan={tambah} />. Di dalam DaftarMenu, tiap<MenuItem />ditulis tanpaon:pesan. MenuItem mengirim eventpesansaat tombolnya diklik. Apakahtambahdi App jalan? Jawabannya ada di bagian "Meneruskan event (forwarding)".
createEventDispatcher: anak mengirim kabar
Ini MenuItem.svelte dari pelajaran Props, sekarang dengan tombol Pesan:
<!-- MenuItem.svelte -->
<script>
import { createEventDispatcher } from "svelte"
export let nama
export let harga
const dispatch = createEventDispatcher()
function pesan() {
dispatch("pesan", { nama, harga })
}
</script>
<h3>{nama}</h3>
<p>Rp {harga.toLocaleString("id-ID")}</p>
<button on:click={pesan}>Pesan</button>
createEventDispatcherdiimpor dari"svelte", lalu dipanggil sekali di<script>saat komponen dibuat. Hasilnya sebuah function pengirim event, biasanya dinamaidispatch.dispatch("pesan", { nama, harga })mengirim event bernamapesan. Argumen kedua adalah data yang ikut terkirim, disebut detail. Isinya boleh objek apa pun, misalnyadispatch("submit", { data: value }).- MenuItem gak tahu siapa induknya dan gak memanggil function induk. Dia cuma mengumumkan.
on:pesan dan event.detail
<!-- App.svelte -->
<script>
import MenuItem from "./MenuItem.svelte"
let keranjang = []
function tambah(event) {
keranjang = [...keranjang, event.detail.nama]
}
</script>
<p>Keranjang: {keranjang.length} menu</p>
<MenuItem nama="Nasi Goreng" harga={15000} on:pesan={tambah} />
<MenuItem nama="Es Teh" harga={5000} on:pesan={tambah} />
| Kejadian | event.detail |
Tampil |
|---|---|---|
| Klik Pesan di Nasi Goreng | { nama: "Nasi Goreng", harga: 15000 } |
Keranjang: 1 menu |
| Klik Pesan di Es Teh | { nama: "Es Teh", harga: 5000 } |
Keranjang: 2 menu |
on:pesan={tambah}ditulis sepertion:clickdi pelajaran Event Handling, tapi nama event-nya buatanmu sendiri.- Handler menerima sebuah
CustomEvent. Data kiriman anak ada dievent.detail, jadi namanya dibacaevent.detail.nama, bukanevent.nama. dispatch("pesan")tanpa argumen kedua membuatevent.detailbernilainull, danevent.detail.namagagal denganTypeError: Cannot read properties of null (reading 'nama').- Nama event dicocokkan persis, huruf besar-kecilnya juga:
dispatch("Pesan")gak didengaron:pesan.
Event tanpa pendengar
<MenuItem nama="Mie Ayam" harga={12000} /> ditulis tanpa on:pesan. Klik Pesan-nya: dispatch tetap jalan, tapi gak ada yang mendengar. Gak ada error, gak ada warning, bahkan di mode dev, dan keranjangnya gak berubah. Jadi kalau klik tombol gak berefek apa-apa, cek dulu apakah induknya memang mendengarkan nama event yang sama.
Meneruskan event (forwarding)
Sekarang kartunya dibungkus DaftarMenu.svelte, dan App cuma memakai <DaftarMenu on:pesan={tambah} />:
<!-- DaftarMenu.svelte -->
<script>
import MenuItem from "./MenuItem.svelte"
</script>
<MenuItem nama="Nasi Goreng" harga={15000} on:pesan />
<MenuItem nama="Mie Ayam" harga={12000} on:pesan />
<MenuItem nama="Es Teh" harga={5000} on:pesan />
- Event komponen gak bubble: dia cuma sampai ke induk langsungnya, yaitu DaftarMenu. Tanpa
on:pesandi DaftarMenu,tambahdi App gak pernah jalan. Itu jawaban tebakannya. on:pesantanpa={...}artinya "teruskan event ini ke induk-ku". Ini disebut event forwarding. App menerima event yang sama, denganevent.detailyang sama.- Event DOM juga bisa diteruskan.
<MenuItem on:click={catat} />gak mendengar klik apa pun di dalam MenuItem, kecuali tombolnya menulis<button on:click on:click={pesan}>:on:clickyang pertama meneruskan klik itu ke induk.
Latihan: tombol yang mengirim klik
<script>
import { createEventDispatcher } from "svelte"
const dispatch = createEventDispatcher()
</script>
<button on:click={() => dispatch("klik", { pesan: "Halo!" })}>
Klik saya
</button>
Penguji latihannya berperan sebagai induk yang mendengarkan event klik, lalu menekan tombolnya. Karena itu nama event-nya harus persis klik.
Svelte 5: callback props
Di Svelte 5, createEventDispatcher sudah usang (deprecated), walau masih jalan di komponen non-runes. Gantinya callback prop, function yang dikirim induk sebagai prop biasa:
<!-- MenuItem.svelte, Svelte 5 -->
<script>
let { nama, harga, onpesan } = $props()
</script>
<button onclick={() => onpesan?.({ nama, harga })}>Pesan</button>
Induknya menulis <MenuItem nama="Nasi Goreng" harga={15000} onpesan={tambah} />, dan tambah(menu) langsung menerima { nama, harga }, tanpa event.detail. ?. membuat panggilan itu diam saat induk gak mengirim onpesan. Meneruskan berarti mengoper prop-nya ke bawah: <MenuItem {nama} {harga} {onpesan} />.
⚠️ Jebakan yang sering ketemu
- Lupa meneruskan. Komponen di tengah harus menulis
on:pesan, kalau gak, kabarnya berhenti di situ tanpa error. - Membaca
event.nama. Hasilnyaundefined. Data kiriman ada dievent.detail. - Membuat dispatcher di dalam handler.
createEventDispatcher()di dalampesan()gagal denganFunction called outside component initialization. Panggil sekali di<script>, sama sepertionMount. - Nama event yang beda. Latihannya mendengarkan persis
klik, dan memeriksacreateEventDispatcher,dispatch, danon:clickdi kodemu.
TL;DR: Anak mengirim kabar ke induk dengan const dispatch = createEventDispatcher() (dari "svelte", dipanggil sekali di <script>), lalu dispatch("pesan", { nama, harga }). Induk mendengarkan dengan on:pesan={tambah} dan membaca datanya di event.detail; tanpa argumen kedua, event.detail adalah null. Event tanpa pendengar gak berbuat apa-apa, tanpa error. Event komponen gak bubble, jadi komponen di tengah meneruskannya dengan on:pesan tanpa handler (event forwarding). Di Svelte 5, createEventDispatcher diganti callback prop seperti onpesan. Latihannya: tombol dengan on:click={() => dispatch("klik", { pesan: "Halo!" })}.