Store adalah wadah nilai yang hidup di luar komponen. Banyak komponen bisa membacanya dan mengubahnya, dan setiap komponen yang berlangganan (subscribe) dikabari tiap isinya berubah. Bayangin grup WhatsApp "Keranjang Warung Bu Sri". Pelayan menulis pesanan baru di grup, dan kasir serta koki langsung dapat notifikasinya, walaupun mereka gak saling menelepon. Anggota yang pulang keluar dari grup, supaya HP-nya gak terus berbunyi. Store bekerja persis begitu: menulis ke store adalah kirim pesan, subscribe adalah masuk grup, dan unsubscribe adalah keluar grup.
🧪 Tebakan cepat:
Header.sveltemengimpor storekeranjanglalu menulis<p>Keranjang: {keranjang}</p>, tanpa tanda$. Yang tampilKeranjang: [],Keranjang: [object Object], atau error? Jawabannya ada di bagian "Tanda $: berlangganan otomatis".
Kenapa butuh store
Di Warung Bu Sri, DaftarMenu.svelte punya tombol Tambah, dan Header.svelte menampilkan jumlah isi keranjang. Keduanya anak dari App, jadi gak bisa saling kirim props. Kamu bisa menaruh keranjang di App lalu mengopernya ke dua anak, tapi makin banyak komponen yang butuh, makin panjang operannya. Store menaruh nilainya di file sendiri, dan komponen mana pun tinggal mengimpornya.
writable: membuat store
// stores.js
import { writable } from "svelte/store"
export const keranjang = writable([])
writable(nilaiAwal)membuat store yang bisa dibaca dan ditulis. Nilai awalnya array kosong.writablediimpor dari modul"svelte/store", bukan dari"svelte".- Yang kembali adalah objek dengan tiga method:
| Method | Gunanya |
|---|---|
subscribe(fn) |
memanggil fn sekarang dengan nilai saat ini, lalu tiap nilainya berubah. Mengembalikan function untuk berhenti berlangganan. |
set(nilai) |
mengganti nilainya dengan nilai baru |
update(fn) |
menghitung nilai baru dari nilai lama: update((k) => [...k, "Es Teh"]) |
Tanda $: berlangganan otomatis
Di file .svelte, tulis $ di depan nama store untuk membaca nilainya:
<!-- DaftarMenu.svelte -->
<script>
import { keranjang } from "./stores.js"
const menu = ["Nasi Goreng", "Mie Ayam", "Es Teh"]
</script>
{#each menu as nama}
<button on:click={() => keranjang.update((k) => [...k, nama])}>Tambah {nama}</button>
{/each}
<button on:click={() => keranjang.set([])}>Kosongkan</button>
<!-- Header.svelte -->
<script>
import { keranjang } from "./stores.js"
</script>
<p>Keranjang: {$keranjang.length} item</p>
| Kejadian | Isi keranjang |
Header |
|---|---|---|
| Awal | [] |
Keranjang: 0 item |
| Klik Tambah Nasi Goreng | ["Nasi Goreng"] |
Keranjang: 1 item |
| Klik Tambah Es Teh | ["Nasi Goreng", "Es Teh"] |
Keranjang: 2 item |
| Klik Kosongkan | [] |
Keranjang: 0 item |
$keranjangmembuat Svelte berlangganan saat Header dibuat, dan berhenti sendiri saat Header dilepas. Kamu gak menulissubscribesama sekali.$juga bisa ditulis:$count += 1sama dengancount.set($count + 1), dan<input bind:value={$name} />mengisi storenametiap kamu mengetik.- Tanpa
$, yang dibaca store-nya sendiri, yaitu objek berisi tiga method tadi. Objek yang dijadikan teks berbunyi[object Object], jadi Header menampilkanKeranjang: [object Object]. Itu jawaban tebakannya. $cuma dipahami compiler di file.svelte, untuk store yang dibuat atau diimpor di level paling atas<script>. Store yang dibuat di dalam function lalu dibaca dengan$ditolak compiler:Stores must be declared at the top level of the component. Di file.js,$keranjangcuma variabel yang gak pernah dibuat.
subscribe manual dan unsubscribe
Kadang kamu butuh nilainya di dalam <script>, atau di file .js. Panggil subscribe sendiri, dan simpan function yang dikembalikannya:
<script>
import { onDestroy } from "svelte"
import { keranjang } from "./stores.js"
let isi = []
const unsubscribe = keranjang.subscribe((k) => {
isi = k
})
onDestroy(unsubscribe)
</script>
<p>Keranjang: {isi.length} item</p>
Kalau onDestroy(unsubscribe) dilupakan, Header yang sudah dilepas tetap terdaftar sebagai pelanggan, dan callback-nya terus dipanggil tiap keranjang berubah. Pasang dan lepas Header tiga kali, dan store punya tiga pelanggan, dua di antaranya komponen yang sudah gak ada. Ini kebocoran yang sama dengan interval di pelajaran Lifecycle. $keranjang gak punya masalah ini.
readable: store yang cuma bisa dibaca
readable(nilaiAwal, mulai) membuat store tanpa set dan update dari luar. Nilainya cuma diubah oleh function mulai di dalamnya:
import { readable } from "svelte/store"
export const jam = readable(new Date().toLocaleTimeString("id-ID"), (set) => {
const timer = setInterval(() => set(new Date().toLocaleTimeString("id-ID")), 1000)
return () => clearInterval(timer)
})
mulaijalan saat pelanggan pertama datang, dan function yang dikembalikannya jalan saat pelanggan terakhir pergi. Jadi interval-nya cuma hidup selama ada yang menampilkan{$jam}.- Komponen yang mencoba
jam.set("07.00")mendapatTypeError: jam.set is not a function. Bedanya dengan writable: readable gak bisa diubah dari luar.
Latihan: count dan name
Latihan pertama, penghitung dengan store:
<script>
import { writable } from "svelte/store"
const count = writable(0)
</script>
<p>Count: {$count}</p>
<button on:click={() => $count += 1}>+1</button>
Awalnya Count: 0, satu klik jadi Count: 1, dua klik jadi Count: 2. Latihan kedua, sapaan yang ikut kolom isian:
<script>
import { writable } from "svelte/store"
const name = writable("Dunia")
</script>
<input bind:value={$name} />
<h1>Halo, {$name}!</h1>
Awalnya Halo, Dunia!. Ketik Sinta, dan judulnya jadi Halo, Sinta!.
Svelte 5
Store tetap jalan di Svelte 5, termasuk $keranjang di komponen runes. Svelte 5 juga punya cara lain untuk state bersama: $state di file .svelte.js, misalnya export const keranjang = $state([]), lalu komponen mana pun memanggil keranjang.push("Es Teh"). Untuk jalur ini, store dari svelte/store sudah cukup dan masih banyak dipakai.
⚠️ Jebakan yang sering ketemu
- Lupa
$.{keranjang}menampilkan[object Object], karena yang dibaca store-nya, bukan isinya. subscribetanpaunsubscribe. Komponen yang sudah dilepas terus dikabari. Simpan nilai kembaliannya dan panggil dionDestroy, atau pakai$keranjang.$di file.jsatau di store yang dibuat di dalam function. Di.jshasilnyaReferenceError; di dalam function compiler menolak. Buat store di level atas, dan di.jspakaisubscribeatauget(keranjang).- Mengganti teksnya, bukan store-nya. Tombol
+1di latihan harus mengubah store ($count += 1), bukan menulis angka baru di paragraf. Penguji juga mencariwritable(danon:clickdi kodemu.
TL;DR: Store adalah nilai di file sendiri yang bisa dibaca dan diubah banyak komponen. writable([]) dari "svelte/store" memberi subscribe, set, dan update. Di file .svelte, {$keranjang} berlangganan saat komponen dibuat dan berhenti saat dilepas; tanpa $, yang tampil [object Object]. $count += 1 dan bind:value={$name} menulis ke store. subscribe manual harus dipasangkan dengan unsubscribe di onDestroy, kalau gak, komponen yang sudah dilepas terus dikabari. readable gak bisa diubah dari luar; nilainya diatur function di dalamnya. Latihannya: const count = writable(0) dengan <p>Count: {$count}</p> dan tombol +1 yang membuatnya Count: 1 lalu Count: 2, dan const name = writable("Dunia") dengan <input bind:value={$name} /> dan <h1>Halo, {$name}!</h1>.