Svelte 5 Runes — Svelte

Sejauh ini kamu belajar Svelte 4: variable biasa itu reactive, $: untuk derived, writable() untuk cross-component state. Svelte 5 memperkenalkan runes —…

Runes adalah kata kunci Svelte 5 yang diawali $ dan dipanggil seperti function, misalnya $state, $derived, $effect, $props, dan $bindable. Dengan runes, kamu menandai sendiri mana yang reaktif, alih-alih Svelte menebak dari let dan $:. Bayangin papan stok di dapur Warung Bu Sri. Dulu, pegawai cuma memperbarui papan kalau ada yang menulis ulang angka di buku dengan tanda sama dengan; kalau ada yang diam-diam menambah isi toples, papannya diam. Sekarang Bu Sri menempel stiker di toples yang harus dipantau. Toples berstiker diawasi terus, termasuk saat isinya ditambah, angka di papan yang dihitung dari toples itu ikut berubah sendiri, dan bel dapur berbunyi tiap ada perubahan. Stiker itu $state, angka di papan $derived, dan bel dapur $effect.

🧪 Tebakan cepat: di pelajaran Proyek, belanja.push(barang) gak memperbarui layar di Svelte 4. Sekarang ditulis let keranjang = $state([]), lalu tombolnya menjalankan keranjang.push("Es Teh"). Apakah Keranjang: 1 menu muncul? Jawabannya ada di bagian "Deep reactivity: push sekarang jalan".

$state dan $derived

<script>
  let count = $state(0)
  let double = $derived(count * 2)
</script>

<p class="count">Count: {count}</p>
<p class="double">Double: {double}</p>
<button onclick={() => count += 1}>+1</button>

Satu klik di compiler Svelte 5.16: Count: 0 dan Double: 0 jadi Count: 1 dan Double: 2.

Deep reactivity: push sekarang jalan

<script>
  let keranjang = $state([])
  function tambah() { keranjang.push("Es Teh") }
</script>

<p>Keranjang: {keranjang.length} menu</p>
<button onclick={tambah}>Tambah</button>
Klik Svelte 4 (let keranjang = [], on:click) Svelte 5 ($state([]))
awal Keranjang: 0 menu Keranjang: 0 menu
1 Keranjang: 0 menu Keranjang: 1 menu
2 Keranjang: 0 menu Keranjang: 2 menu

Ya, Keranjang: 1 menu muncul. Itu jawaban tebakannya. $state dengan array atau objek membungkusnya dalam proxy, yaitu objek perantara yang mencatat tiap kali isinya dibaca atau diubah, sampai ke dalam-dalamnya. Jadi push, keranjang[0].porsi += 1, dan splice semua memperbarui layar. Aturan Svelte 4, "cuma assignment yang memperbarui layar", gak berlaku lagi untuk $state.

Akibatnya, console.log(keranjang) mencetak proxy, bukan array biasa, dan mode dev memberi warning: Your `console.log` contained `$state` proxies. Consider using `$inspect(...)` or `$state.snapshot(...)` instead. console.log($state.snapshot(keranjang)) mencetak [ 'Es Teh' ].

$effect dan cleanup

$effect menjalankan kode samping (log, timer, document.title, localStorage) setelah DOM diperbarui, dan jalan lagi tiap kali state yang dibacanya berubah:

<script>
  let count = $state(0)

  $effect(() => {
    const n = count
    console.log("effect jalan, count =", n)
    return () => console.log("cleanup, count tadi =", n)
  })
</script>

<button onclick={() => count += 1}>+1</button>

Console-nya, dari dipasang, satu klik, lalu komponennya dilepas:

effect jalan, count = 0
cleanup, count tadi = 0
effect jalan, count = 1
cleanup, count tadi = 1

$props, $bindable, dan event sebagai prop

<!-- MenuItem.svelte -->
<script>
  let { nama, harga = 0, onpesan } = $props()
</script>

<p>{nama}: Rp {harga}</p>
<button onclick={() => onpesan?.({ nama, harga })}>Pesan</button>
<!-- InputPorsi.svelte -->
<script>
  let { value = $bindable(1) } = $props()
</script>

<input type="number" bind:value />

Induknya menulis <MenuItem nama="Mie Ayam" harga={12000} onpesan={(m) => pesanan.push(m)} /> dan <InputPorsi bind:value={porsi} />.

Snippet dan {@render}: pengganti slot

<!-- Kartu.svelte -->
<script>
  let { judul, children } = $props()
</script>

<div class="kartu">
  <h3>{@render judul()}</h3>
  {@render children()}
</div>
<Kartu>
  {#snippet judul()}Mie Ayam{/snippet}
  <p>Rp 12000</p>
</Kartu>

Hasilnya <div class="kartu"><h3>Mie Ayam</h3> <p>Rp 12000</p></div>. {#snippet judul()} mengganti slot bernama, dan isi lain di antara tag komponen otomatis jadi prop children, pengganti <slot />.

Peta Svelte 4 ke Svelte 5

Jalur ini (Svelte 4) Svelte 5 dengan runes
let count = 0 let count = $state(0)
$: double = count * 2 let double = $derived(count * 2)
$: console.log(count) $effect(() => console.log(count))
export let nama let { nama } = $props()
export let value, lalu bind:value dari induk let { value = $bindable() } = $props()
on:click={f} onclick={f}
on:submit dengan modifier preventDefault onsubmit={(e) => { e.preventDefault(); f() }}
createEventDispatcher, on:pesan prop function onpesan
<slot />, <slot name="judul" /> {@render children()}, {#snippet judul()} dan {@render judul()}
<svelte:component this={X} /> <X />
beforeUpdate, afterUpdate $effect.pre, $effect
writable dan $keranjang tetap jalan, atau $state di file .svelte.js
$app/stores $app/state (SvelteKit)

Yang gak berubah: {#if}, {#each}, {#await}, bind:, class:, use:, transition:, animate:, onMount, setContext dan getContext, serta <svelte:window> dan <svelte:head>.

Rune di file .svelte.js

Runes juga jalan di luar komponen, di file berakhiran .svelte.js. Keranjang bisa jadi state bersama tanpa writable():

// src/lib/keranjang.svelte.js
export const keranjang = $state([])

export function tambah(nama) {
  keranjang.push({ nama, porsi: 1 })
}

Komponen mana pun yang mengimpornya dan membaca keranjang.length ikut diperbarui. Akhiran .svelte.js wajib, karena cuma file itu yang lewat compiler Svelte. State yang diekspor juga gak boleh diisi ulang: export let keranjang = $state([]) lalu keranjang = [] ditolak dengan Cannot export state from a module if it is reassigned. Ubah isinya, misalnya keranjang.length = 0.

Latihan: counter dengan runes

Editor latihan memakai Svelte 5 begitu kodemu memakai rune seperti $state(. Tulis let count = $state(0) dan let double = $derived(count * 2), lalu <p class="count">Count: {count}</p>, <p class="double">Double: {double}</p>, dan <button onclick={() => count += 1}>+1</button>. Sebelum diklik, halamannya Count: 0 dan Double: 0; setelah satu klik, Count: 1 dan Double: 2.

⚠️ Jebakan yang sering ketemu

TL;DR: Runes menandai reaktivitas secara eksplisit. let count = $state(0) menggantikan let count = 0, let double = $derived(count * 2) menggantikan $: double = count * 2, dan $effect(() => {...}) menggantikan $: untuk efek samping, dengan function yang di-return sebagai cleanup sebelum run berikutnya dan saat komponen dilepas. $state dengan array atau objek adalah proxy yang reaktif sampai ke dalam, jadi keranjang.push(...) memperbarui layar, berbeda dari aturan assignment Svelte 4; cetak isinya dengan $state.snapshot. let { nama, onpesan } = $props() menggantikan export let dan createEventDispatcher, $bindable() mengizinkan bind: dari induk, onclick menggantikan on:click, dan {#snippet} dengan {@render} menggantikan slot. Runes juga jalan di file .svelte.js, jadi state bersama gak perlu writable(). Latihannya: $state(0), $derived(count * 2), <p class="count">, <p class="double">, dan tombol +1 dengan onclick, dari Count: 0, Double: 0 jadi Count: 1, Double: 2.

Yang akan kamu pelajari