Store cuma bisa diubah lewat setter-nya. Untuk perubahan yang lebih dari satu langkah, "solid-js/store" punya dua pembantu: produce, yang membiarkan kamu menulis perubahan seperti mengubah objek biasa lalu menerapkan semuanya sekaligus, dan reconcile, yang memasang data baru dari server tanpa membuang objek yang masih sama. Bayangin Kak Nadia dan petugas rak. Kalau tiap koreksi diteriakkan satu per satu, petugas rak bolak-balik merapikan rak sesudah tiap teriakan. produce itu menulis semua koreksi di satu catatan lalu menyerahkannya sekali. Lalu tiap pagi pemasok mengirim katalog baru. Kak Nadia gak membuang semua kartu dan mencetak ulang. Dia mencocokkan katalog baru dengan kartu yang ada lewat kode bukunya, dan cuma mengganti angka yang beda. Itu reconcile.
🧪 Tebakan cepat: tombol
Pesan Laskar PelangimemanggilsetTokodua kali: stoknya dikurangi, lalu judulnya ditambahkan ke keranjang. Sebuah efek mencetakringkasan: ... stok, ... di keranjang. Satu klik, berapa barisringkasanyang tercetak: 1 atau 2? Jawabannya ada di bagian "Satu klik, berapa kali update".
Store dibaca dari mana saja, diubah lewat setter
const [state, setState] = createStore({ items: [1, 2, 3] })
state.items.push(4)
Ini gak menambah apa pun: state.items tetap [1, 2, 3]. Store bersifat read-only dari luar setter-nya, jadi push di atas gagal tanpa error, dan di mode development console menulis Cannot mutate a Store directly dua kali (sekali untuk items[3], sekali untuk length). Yang benar: lewat setState, atau produce di dalamnya.
produce: tulis seperti objek biasa
import { createStore, produce } from "solid-js/store"
function App() {
const [state, setState] = createStore({ items: ["A", "B"] })
const addItem = () => {
setState(produce(s => {
s.items.push("C")
}))
}
return (
<div>
<p>Items: {state.items.join(", ")}</p>
<button onClick={addItem}>Tambah</button>
</div>
)
}
| Kejadian | state.items |
Paragraf |
|---|---|---|
| Halaman dibuka | ["A", "B"] |
Items: A, B |
Klik Tambah |
["A", "B", "C"] |
Items: A, B, C |
producediimpor dari"solid-js/store", sama seperticreateStore.produce(s => { ... })membuat fungsi pengubah. Di dalamnya,sboleh diubah seperti objek biasa:push,s.items[0] = "X",s.user.name = "Budi".- Fungsi itu harus dikirim ke setter:
setState(produce(...)).produce(...)sendirian gak mengubah apa-apa. join(", ")merangkai item dengan koma dan spasi. Ini latihan pelajaran ini:Items: A, Bsebelum diklik,Items: A, B, Csesudahnya.
Satu klik, berapa kali update
Toko Buku Kak Nadia dengan keranjang di store dan efek ringkasan:
createEffect(() => {
console.log("ringkasan:", toko.buku[0].stok, "stok,", toko.keranjang.length, "di keranjang")
})
// Versi A: dua setter
const pesan = () => {
setToko("buku", 0, "stok", s => s - 1)
setToko("keranjang", k => [...k, toko.buku[0].judul])
}
// Versi B: satu produce
const pesan = () => {
setToko(produce(t => {
t.buku[0].stok -= 1
t.keranjang.push(t.buku[0].judul)
}))
}
Diuji di Solid 1.9, klik Pesan Laskar Pelangi sekali:
| Versi | Console |
|---|---|
A, dua setToko |
ringkasan: 3 stok, 0 di keranjang, lalu ringkasan: 3 stok, 1 di keranjang |
B, satu produce |
ringkasan: 3 stok, 1 di keranjang |
Dua setter, dua update. Itu jawaban tebakannya. Solid 1.9 gak mengumpulkan setter di dalam handler klik: tiap setToko langsung menjalankan semua yang membaca bagian itu. Di antara keduanya ada keadaan setengah jadi, stoknya sudah turun tapi keranjangnya belum bertambah, dan efeknya sempat melihatnya. produce menerapkan semua perubahannya dalam satu update. Untuk beberapa setter terpisah, signal juga, bungkus dengan batch dari "solid-js": batch(() => { setToko(...); setToko(...) }) juga cuma mencetak satu baris.
reconcile: data baru, objek lama
Katalog baru dari server: Laskar Pelangi tetap 4, Bumi Manusia jadi 5, Cantik Itu Luka jadi 3, ditambah Ronggeng Dukuh Paruk. Tiap buku punya id. Ada dua cara memasangnya:
import { reconcile } from "solid-js/store"
setToko("buku", ambilKatalog()) // ganti
setToko("buku", reconcile(ambilKatalog())) // cocokkan
Dengan catatan kado di kartu Laskar Pelangi, diuji di Solid 1.9:
| ganti | reconcile |
|
|---|---|---|
| Baris yang dibuat | 4, semua kartu dibuat ulang | 1, cuma Ronggeng Dukuh Paruk |
| Teks yang diganti | semua, lewat baris baru | 3, cuma stok yang berubah |
Catatan kado |
hilang | tetap |
- Tanpa
reconcile, array baru berisi objek baru.<For>mengenali baris dari objeknya, jadi semua baris dibuat ulang. reconcilemembandingkan data baru dengan isi store per item, dicocokkan lewatid. Item yang sama tetap objek yang sama, dan cuma properti yang beda yang diganti. Item baru dapat baris baru.- Kalau kuncinya bukan
id, sebut namanya:reconcile(data, { key: "kode" }). Tanpa itu, item dicocokkan menurut posisinya: kalau server mengirim urutan lain, isi baris yang diganti, dan catatankadobisa tertinggal di sebelah buku lain.
⚠️ Jebakan yang sering ketemu
- Mengubah store di luar setter.
toko.keranjang.push(...)atautoko.buku[0].stok = 3gak berpengaruh. Tulis di dalamsetToko(produce(t => { ... })). producetanpa setter.produce(s => { s.items.push("C") })cuma membuat fungsi. Kirim ke setter:setState(produce(...)).- Mengembalikan array baru dari
produce.produce(s => [...s.items, "C"])gak berpengaruh, karena nilai kembaliannya diabaikan. Di dalamproduce, ubahs, jangan kembalikan nilai baru. - Data server dipasang tanpa
reconcile. Jalan, tapi semua baris dibuat ulang dan isi input di dalamnya hilang. - Teks yang gak persis. Latihan ini membandingkan seluruh paragraf:
Items: A, B, C, dengan koma dan spasi darijoin(", ").
TL;DR: Store gak bisa diubah langsung (state.items.push(4) gak berpengaruh, console development menulis Cannot mutate a Store directly). setState(produce(s => { s.items.push("C") })) membiarkan kamu menulis perubahan seperti objek biasa, lalu menerapkannya dalam satu update. Dua setToko berturut-turut di handler klik berarti dua update, dengan keadaan setengah jadi di antaranya; produce atau batch membuatnya satu. setToko("buku", reconcile(dataBaru)) memasang data dari server dengan mencocokkan id: objek yang sama dipertahankan, cuma yang beda diganti, jadi baris dan isinya tetap.