Effect adalah kode yang jalan karena sebuah signal berubah, untuk pekerjaan di luar tampilan: menyimpan keranjang ke localStorage, mengganti judul tab, menulis ke console. Pekerjaan seperti itu disebut side effect. createEffect(fn) menjalankan fn sekali sesudah komponen tampil, lalu lagi tiap kali signal yang dibaca di dalam fn berubah. Bayangin asisten Kak Nadia yang tugasnya menyalin angka keranjang ke buku catatan setiap kali angkanya berubah. Dia gak dikasih daftar apa yang harus diawasi. Dia mengawasi persis angka yang dia lirik waktu terakhir menyalin. Angka yang gak dia lirik, misalnya isi kotak pencarian, gak membuatnya bergerak.
🧪 Tebakan cepat: efek keranjang ditulis begini:
createEffect(() => { setTimeout(() => localStorage.setItem("keranjang", jumlah()), 0) }). Tombol+1 Bukudiklik dua kali. IsilocalStorageuntuk"keranjang"jadi"0"atau"2"? Jawabannya ada di bagian "Yang dilacak cuma bacaan saat efeknya jalan".
createEffect: jalan sesudah render, lalu tiap signal-nya berubah
Keranjang Toko Buku Kak Nadia, sekarang dengan efek yang menyimpannya:
import { createSignal, createEffect } from "solid-js"
function App() {
const [jumlah, setJumlah] = createSignal(0)
const [cari, setCari] = createSignal("")
console.log("App jalan")
createEffect(() => {
localStorage.setItem("keranjang", jumlah())
console.log("simpan:", jumlah())
})
return (
<div>
<button onClick={() => setJumlah(j => j + 1)}>+1 Buku</button>
<p>Keranjang: {jumlah()} buku</p>
<input value={cari()} onInput={e => setCari(e.target.value)} placeholder="Cari judul" />
<p>Mencari: {cari()}</p>
</div>
)
}
| Kejadian | jumlah() |
Console | localStorage "keranjang" |
|---|---|---|---|
| Halaman dibuka | 0 | App jalan, lalu simpan: 0 |
"0" |
Klik +1 Buku |
1 | simpan: 1 |
"1" |
Ketik Bumi di kotak cari |
1 | (gak ada) | "1" |
| Klik lagi | 2 | simpan: 2 |
"2" |
createEffect(() => { ... })menerima satu fungsi. Solid menjalankannya sekali sesudahAppselesai dan elemennya sudah ada di halaman, jadisimpan: 0tercetak sesudahApp jalan.- Selama fungsi itu jalan, Solid mencatat signal yang dibaca: di sini
jumlah(). Itu daftar awasannya. Gak ada dependency array sepertiuseEffectdi React. - Tiap
setJumlahmengubah nilainya, efeknya jalan lagi.localStoragemenyimpan teks, jadi angka2tersimpan sebagai"2". - Mengetik di kotak cari mengubah
caridan teksMencari, tapi efek ini gak membacacari(), jadi dia diam.
Bentuk latihannya: judul tab
Latihan pelajaran ini memakai efek untuk menulis judul dokumen, teks yang tampil di tab browser:
import { createSignal, createEffect } from "solid-js"
function App() {
const [count, setCount] = createSignal(0)
createEffect(() => {
document.title = `Count: ${count()}`
})
return (
<div>
<p>Count: {count()}</p>
<button onClick={() => setCount(c => c + 1)}>+1</button>
</div>
)
}
document.title = ...mengganti judul tab. Itu side effect, jadi tempatnya di dalam efek.`Count: ${count()}`adalah template literal:count()dibaca di dalam efek, jadi efeknya ikut berubah. JudulnyaCount: 0saat halaman tampil,Count: 1sesudah sekali klik,Count: 2sesudah dua kali.<p>Count: {count()}</p>membaca signal yang sama, jadi paragrafnya juga ikut. Tombol+1menambah dari nilai sebelumnya.
Yang dilacak cuma bacaan saat efeknya jalan
Solid mencatat bacaan selama fungsi efeknya sedang jalan, dari baris pertama sampai baris terakhir. Bacaan yang terjadi belakangan gak tercatat:
createEffect(() => {
setTimeout(() => {
localStorage.setItem("keranjang", jumlah())
}, 0)
})
Fungsi efeknya cuma memasang timer lalu selesai. jumlah() baru dibaca sesaat kemudian, saat efeknya sudah gak jalan, jadi Solid gak mencatat apa pun. Efeknya jalan sekali, menyimpan "0", lalu gak pernah jalan lagi. Dua klik kemudian keranjangnya 2, tapi localStorage tetap "0". Itu jawaban tebakannya.
Hal yang sama berlaku untuk bacaan sesudah await di efek async. Jadi baca signal-nya langsung di badan efek, lalu kirim nilainya ke kode yang jalan belakangan: const j = jumlah() dulu, baru setTimeout(() => localStorage.setItem("keranjang", j), 0).
Catatannya juga dibuat ulang tiap jalan. Efek if (aktif()) console.log(jumlah()) yang sedang aktif() bernilai false gak membaca jumlah(), jadi saat itu dia cuma mengawasi aktif.
Satu setter, satu kali jalan ulang
const [a, setA] = createSignal(1)
createEffect(() => console.log(a()))
setA(2)
setA(3)
Console-nya 1, 2, 3. Efek jalan sekali saat dibuat (1), lalu tiap setter yang mengubah nilai menjalankannya lagi saat itu juga (2, lalu 3). Kalau beberapa setter mau dihitung sebagai satu perubahan, bungkus dengan batch(() => { ... }).
💡 on(): pilih sendiri yang diawasi, dan defer
import { on } from "solid-js"
createEffect(on(jumlah, (j, sebelumnya) => {
localStorage.setItem("keranjang", j)
console.log("simpan:", j, "sebelumnya:", sebelumnya)
}, { defer: true }))
on(jumlah, fn)cuma mengawasijumlah. Signal yang dibaca di dalamfngak ikut dilacak.fnmenerima nilai baru dan nilai sebelumnya.{ defer: true }melewati jalan pertama. Saat halaman dibuka gak ada yang tersimpan, dan klik pertama mencetaksimpan: 1 sebelumnya: undefined.
💡 onCleanup: membereskan sebelum jalan lagi
import { onCleanup } from "solid-js"
createEffect(() => {
const j = jumlah()
console.log("simpan:", j)
onCleanup(() => console.log("bersihkan:", j))
})
Fungsi di onCleanup jalan tepat sebelum efeknya jalan lagi, dan saat komponennya dibuang. Klik pertama mencetak bersihkan: 0 lalu simpan: 1. Tempat ini dipakai untuk mematikan setInterval atau melepas listener yang dipasang efeknya.
⚠️ Jebakan yang sering ketemu
- Efek yang menulis signal yang dibacanya.
createEffect(() => setJumlah(jumlah() + 1))mengubahjumlah, yang menjalankan efeknya lagi, yang mengubahjumlahlagi, terus sampai browser menyerah denganRangeError: Maximum call stack size exceeded. Nilai yang dihitung dari signal lain ditulis sebagai fungsi ataucreateMemo(pelajaran berikutnya), bukan efek yang memanggil setter. - Membaca signal di
setTimeoutatau sesudahawait. Bacaan itu gak dilacak, jadi efeknya berhenti di jalan pertama. - Kebiasaan React.
createEffect(() => { ... }, [count])gak error, tapi array itu bukan daftar dependency: argumen keduacreateEffectadalah nilai awal. Yang diawasi tetap bacaan di dalam fungsinya. - Lupa kurung.
`Count: ${count}`menulis teks fungsinya ke judul tab, dan judulnya gak pernah berubah. Tuliscount().
TL;DR: createEffect(() => { ... }) untuk side effect: menyimpan ke localStorage, mengganti document.title, logging. Efeknya jalan sekali sesudah komponen tampil, lalu lagi tiap kali signal yang dibacanya berubah, tanpa dependency array. Yang dilacak cuma bacaan saat fungsinya sedang jalan: bacaan di setTimeout atau sesudah await gak dihitung. createEffect(() => console.log(a())) lalu setA(2), setA(3) mencetak 1, 2, 3. on(signal, fn, { defer: true }) memilih sendiri yang diawasi dan melewati jalan pertama, onCleanup membereskan sebelum jalan berikutnya. Jangan menulis signal yang dibaca efek itu sendiri: hasilnya loop sampai Maximum call stack size exceeded.