Proyek Svelte adalah satu aplikasi yang memakai banyak pelajaran sekaligus: route dan layout dari SvelteKit, data dari load, store untuk keranjang, form action untuk pesanan, dan komponen dengan {#each}, bind:, serta $:. Bayangin Warung Bu Sri di hari pertama buka. Dapur, etalase, kasir, dan papan nama sudah pernah kamu siapkan satu per satu; sekarang semuanya dipasang di satu bangunan dan dipakai bersama. Bangunan itu folder proyeknya, ruangannya route, papan namanya layout, dan buku catatan yang dibaca semua ruangan adalah store.
🧪 Tebakan cepat: di daftar belanja, fungsi
tambah()ditulisbelanja.push(barang)alih-alihbelanja = [...belanja, barang]. Kamu mengetik "Beras" lalu menekan Tambah. Beras muncul di daftar atau gak? Jawabannya ada di bagian "Kenapa belanja = [...belanja, barang]".
Peta proyek Warung Bu Sri
Semua file ini sudah kamu tulis di pelajaran sebelumnya. Di satu proyek, mereka bertemu:
| File | Isinya | Dari pelajaran |
|---|---|---|
src/lib/menu.js |
data menu, pura-pura database | Load Data |
src/lib/keranjang.js |
custom store: tambah, hapus, kosongkan |
Custom Stores |
src/routes/+layout.svelte |
header, nav, jumlah keranjang, <slot /> |
Routing, Stores |
src/routes/menu/+page.js dan +page.svelte |
load menu, tombol Tambah ke keranjang |
Load Data, Event |
src/routes/pesan/+page.server.js dan +page.svelte |
form pesanan dengan fail dan redirect |
Form Actions |
src/routes/belanja/+page.svelte |
daftar belanja Bu Sri ke pasar | pelajaran ini |
Layout-nya membaca store, jadi jumlah keranjang tampil di semua halaman:
<!-- src/routes/+layout.svelte -->
<script>
import { keranjang } from "#lib/keranjang.js"
</script>
<header>
<h1>Warung Bu Sri</h1>
<nav>
<a href="/menu">Menu</a>
<a href="/belanja">Belanja</a>
</nav>
<p>Keranjang: {$keranjang.length} menu</p>
</header>
<slot />
<!-- src/routes/menu/+page.svelte -->
<script>
import { keranjang } from "#lib/keranjang.js"
export let data
</script>
<h2>Menu</h2>
<ul>
{#each data.menu as m (m.id)}
<li>{m.nama} <button on:click={() => keranjang.tambah(m.nama)}>Tambah</button></li>
{/each}
</ul>
Satu halaman utuh: daftar belanja
Halaman baru proyek ini adalah daftar belanja Bu Sri ke pasar. Dia memakai hampir semua yang ada di jalur Svelte dalam satu komponen:
<!-- src/routes/belanja/+page.svelte -->
<script>
let belanja = []
let input = ""
let nextId = 1
$: sisa = belanja.filter((b) => !b.beres).length
function tambah() {
if (!input.trim()) return
belanja = [...belanja, { id: nextId++, nama: input, beres: false }]
input = ""
}
function hapus(id) {
belanja = belanja.filter((b) => b.id !== id)
}
</script>
<h2>Belanja ke Pasar</h2>
<p>{sisa} belum dibeli dari {belanja.length}</p>
<form on:submit|preventDefault={tambah}>
<input bind:value={input} placeholder="Mau beli apa?" />
<button>Tambah</button>
</form>
<ul>
{#each belanja as b (b.id)}
<li>
<input type="checkbox" bind:checked={b.beres} />
<span class:beres={b.beres}>{b.nama}</span>
<button on:click={() => hapus(b.id)}>Hapus</button>
</li>
{/each}
</ul>
<style>
.beres { text-decoration: line-through; opacity: 0.5; }
</style>
$: sisa = ...lengthadalah reactive declaration: dihitung ulang tiap kalibelanjaberubah.on:submit|preventDefault={tambah}menjalankantambahdan mencegah browser mengirim form-nya sendiri. Tanpa|preventDefault, halaman Svelte biasa (seperti editor latihan) dimuat ulang dan isi daftarnya hilang. SvelteKit menangkap form GET sebagai navigasi di browser, jadi di proyek ini daftarnya kebetulan selamat; tetap tulis|preventDefault, karena form ini bukan untuk pindah halaman.bind:value={input}mengikat kolom dua arah: mengetik mengisiinput, daninput = ""mengosongkan kolomnya.if (!input.trim()) returnmenolak kolom kosong, supaya gak ada baris kosong di daftar.{#each belanja as b (b.id)}menggambar satu<li>per barang, denganb.idsebagai key.bind:checked={b.beres}danclass:beresmencoret barang yang sudah dibeli.hapusmembuat array baru tanpa barang itu lewatfilter, lalu menyimpannya kembali kebelanja.
Dicoba dengan compiler Svelte 4.2.19:
| Langkah | belanja |
Layar |
|---|---|---|
| awal | [] |
0 belum dibeli dari 0, daftar kosong |
| ketik "Beras", Tambah | 1 barang | 1 belum dibeli dari 1, kolomnya kosong lagi |
| ketik "Telur", Tambah | 2 barang | 2 belum dibeli dari 2 |
| centang Beras | Beras beres: true |
1 belum dibeli dari 2, Beras dicoret |
| Tambah dengan kolom kosong | tetap 2 | gak ada baris baru |
| Hapus Telur | 1 barang | 0 belum dibeli dari 1 |
Kenapa belanja = [...belanja, barang]
Di Svelte 4, halaman diperbarui saat ada assignment ke variabel, yaitu tanda =. belanja.push(barang) mengubah array di memori, tapi gak ada assignment, jadi Svelte gak tahu ada yang berubah: daftar dan sisa tetap 0 belum dibeli dari 0. Jadi Beras gak muncul. Itu jawaban tebakannya. Polanya selalu spread lalu assignment ulang: belanja = [...belanja, barang] untuk menambah, belanja = belanja.filter(...) untuk menghapus. Svelte 5 dengan $state berbeda, dan dibahas di pelajaran Runes.
Yang hidup di halaman, dan yang hidup di store
let belanja hidup di dalam komponen halaman. Pindah dari /belanja ke /menu, SvelteKit melepas komponen itu, dan isinya ikut hilang; kembali ke /belanja, daftarnya kosong lagi. Keranjang gak hilang, karena store keranjang hidup di modul src/lib/keranjang.js, bukan di halaman, dan layout tetap terpasang. Aturannya: data yang cuma dipakai satu halaman cukup let di halaman itu; data yang dibaca halaman lain atau layout dipindah ke store.
Latihan: Todo App dan Shopping List
Kedua latihan adalah daftar seperti di atas, sebagai aplikasi berdiri sendiri, jadi judulnya <h1>.
- Todo App: kode awal sudah punya
todos,input,$: remaining,addTodo(),removeTodo(),<h1>Todo App</h1>, dan<p>{remaining} tersisa</p>. Kamu menambah<form on:submit|preventDefault={addTodo}>berisi<input bind:value={input} />dan<button type="submit">Tambah</button>, lalu<ul>dengan{#each todos as todo (todo.id)}. Penguji mengetikBelajar Svelte, menekan Tambah, dan mencari teks itu di<ul>. - Shopping List: kode awal punya
items,input,<h1>Shopping List</h1>, dan<p>Total: {items.length} item</p>. Kamu menulisaddItem()danremoveItem(id)sendiri, persis sepertitambahdanhapusdi atas, lalu form dan<ul>-nya. Penguji mengetikKopi.
Di keduanya, tombolnya bertuliskan persis Tambah, kolomnya kosong lagi setelah item masuk, dan Tambah dengan kolom kosong gak membuat <li>.
⚠️ Jebakan yang sering ketemu
pushalih-alih assignment. Datanya bertambah di memori, layarnya diam. Tulisitems = [...items, item].- Lupa
|preventDefault. Browser ikut mengirim form-nya. Di editor latihan, halamannya dimuat ulang dan daftarnya hilang. - Kolom gak dikosongkan. Setelah item masuk, tulis
input = ""; penguji memeriksa kolomnya kosong lagi. - Gak menolak kolom kosong. Tanpa
if (!input.trim()) return, Tambah dengan kolom kosong membuat<li>kosong, dan penguji menolaknya. - Data bersama disimpan di
lethalaman. Hilang begitu pindah halaman. Pindahkan ke store kalau halaman lain atau layout membacanya.
TL;DR: Proyek Svelte menyatukan semua pelajaran: route dan +layout.svelte dengan <slot />, load untuk data, store di src/lib untuk data yang dipakai banyak halaman (keranjang di header), form action untuk pesanan, dan komponen halaman. Satu halaman daftar memakai let belanja = [], bind:value={input}, <form on:submit|preventDefault={tambah}>, {#each belanja as b (b.id)}, bind:checked, class:, dan $: sisa = belanja.filter((b) => !b.beres).length. Di Svelte 4, layar cuma diperbarui lewat assignment, jadi menambah ditulis belanja = [...belanja, barang] (spread lalu assignment ulang) dan menghapus belanja = belanja.filter(...); push gak memperbarui layar. Tolak kolom kosong dengan if (!input.trim()) return dan kosongkan dengan input = "". let di halaman hilang saat pindah halaman; store tetap. Latihannya: Todo App dengan kata Belajar Svelte dan Shopping List dengan Kopi, tombolnya Tambah.