Solid mencatat siapa membaca signal apa, lalu menjalankan ulang yang perlu saja. Pelajaran ini memberi alat untuk mengatur catatan itu: batch menggabungkan beberapa perubahan jadi satu putaran, untrack dan on memilih apa yang dilacak, createRoot membuat pemilik yang kamu bongkar sendiri, dan createSelector serta createDeferred menjaga daftar besar tetap ringan. Bayangin mesin kasir Toko Buku Kak Nadia. Kalau struk dicetak tiap satu barang di-scan, pembeli yang membeli dua buku dapat struk setengah jadi di tengah jalan. Kasir yang benar men-scan semuanya dulu, baru mencetak satu struk.
🧪 Tebakan cepat: tombol Update menjalankan
setCount(c => c + 1)lalusetName("Budi"), tanpabatch. Ada efekcreateEffect(() => console.log(name() + ": " + count())). Satu klik dariAndi: 0, apa yang tercetak? Jawabannya ada di bagian "batch: satu putaran untuk banyak perubahan".
batch: satu putaran untuk banyak perubahan
Bentuk latihannya, ditambah satu efek pencatat:
import { createSignal, createEffect, batch } from "solid-js"
function App() {
const [count, setCount] = createSignal(0)
const [name, setName] = createSignal("Andi")
createEffect(() => console.log(name() + ": " + count()))
const updateAll = () => {
batch(() => {
setCount((c) => c + 1)
setName("Budi")
})
}
return (
<div>
<p>{name()}: {count()}</p>
<button onClick={updateAll}>Update</button>
</div>
)
}
Diuji di Solid 1.9, satu klik dari Andi: 0:
updateAll |
Console | Efek jalan |
|---|---|---|
tanpa batch |
Andi: 1, lalu Budi: 1 |
2 kali |
dengan batch |
Budi: 1 |
1 kali |
Tanpa batch, tercetak Andi: 1 lalu Budi: 1. Itu jawaban tebakannya. Tiap setter langsung menjalankan pembacanya, jadi efeknya sempat melihat Andi: 1, keadaan setengah jadi yang gak pernah dimaksud.
batch(() => { ... })menjalankan fungsinya, dan efek yang terpengaruh baru jalan sekali, sesudah fungsinya selesai. Ini juga jawaban kuis: dua setter di dalambatch, efek yang membaca keduanya jalan 1 kali.- Signal-nya sendiri langsung berubah. Membaca
count()di dalambatchsesudahsetCount(5)memberi5. Yang ditunda cuma efeknya. setCount((c) => c + 1)membaca nilai lama, jadi klik kedua memberiBudi: 2. Latihan memeriksa<p>berbunyiAndi: 0, laluBudi: 1, laluBudi: 2.
untrack: membaca tanpa berlangganan
createEffect(() => console.log(untrack(name) + ": " + count()))
Diuji: setName("Budi") gak menjalankan efeknya. setCount(1) sesudahnya mencetak Budi: 1. untrack(fn) memanggil fn tanpa mencatat bacaannya, jadi efek cuma jalan ulang karena count, tapi tetap membaca nama terbaru saat jalan. Signal itu fungsi, jadi untrack(name) cukup, sama dengan untrack(() => name()).
on: menyebut sumbernya sendiri
createEffect(on(count, (nilai, sebelumnya) => {
console.log(sebelumnya, "->", nilai, "· nama:", name())
}))
on(count, fn)cuma melacakcount. Bacaan lain di dalamfn, sepertiname(), gak dilacak.fnmenerima nilai baru dan nilai sebelumnya. Diuji: pertama kali tercetakundefined -> 0 · nama: Andi,setName("Budi")gak mencetak apa pun,setCount(1)mencetak0 -> 1 · nama: Budi.on(count, fn, { defer: true })melewati jalan pertama, jadifnbaru jalan saatcountbenar-benar berubah.
createRoot dan pemilik
Tiap efek dan memo punya pemilik (owner): komponen atau root tempat dia dibuat. Saat pemiliknya dibongkar, efeknya berhenti dan onCleanup-nya jalan. Komponen dibongkar Solid. createRoot membuat pemilik yang kamu bongkar sendiri:
const [count, setCount] = createSignal(0)
const dispose = createRoot((dispose) => {
createEffect(() => console.log("efek:", count())) // efek: 0
onCleanup(() => console.log("dibersihkan"))
return dispose
})
setCount(1) // efek: 1
dispose() // dibersihkan
setCount(2) // gak tercetak apa pun
- Efek yang dibuat di luar pemilik mana pun, misalnya di dalam
setTimeoutatau sesudahawait, gak akan pernah dibongkar. Solid mode development memperingatkannya:computations created outside a `createRoot` or `render` will never be disposed. getOwner()mengambil pemilik sekarang, danrunWithOwner(pemilik, fn)menjalankanfndi bawah pemilik itu. Diuji: di dalamsetTimeout,getOwner()bernilainull, danonCleanupyang dipasang lewatrunWithOwnertetap jalan saat root-nya dibongkar.
Daftar besar: createSelector dan createDeferred
Rak 1.000 buku, dan buku yang dipilih ditandai:
const [dipilih, setDipilih] = createSignal(1)
const isDipilih = createSelector(dipilih)
<For each={rak}>
{(b) => <li classList={{ pilih: isDipilih(b.id) }}>{b.judul}</li>}
</For>
| Tiap baris membaca | Pilih buku lain: yang dihitung ulang |
|---|---|
dipilih() === b.id |
1.000 baris |
isDipilih(b.id) |
2 baris (yang lama dan yang baru) |
Diuji di Solid 1.9 dengan 1.000 efek. createSelector(sumber) mencatat pembacanya per kunci, jadi perubahan cuma membangunkan baris yang hasilnya berubah.
createDeferred(cari, { timeoutMs: 500 }) memberi salinan cari yang menyusul belakangan, saat browser sedang longgar, paling lambat 500 ms. Diuji: tepat sesudah cari diubah tiga kali jadi bumi, salinannya masih "", lalu berubah sekali jadi bumi. Filter rak yang berat membaca salinannya, jadi kotak cari tetap lancar diketik.
⚠️ Jebakan yang sering ketemu
- Lupa
batchsaat mengubah beberapa signal yang dibaca bersama. Efeknya jalan beberapa kali dan sempat melihat keadaan setengah jadi, sepertiAndi: 1. untrack(name())dengan kurung.name()sudah dibaca (dan dilacak) sebelumuntrackjalan, laluuntrackmencoba memanggil"Andi":TypeError: fn is not a function. Tulisuntrack(name).- Efek di dalam
setTimeoutatau sesudahawait. Gak punya pemilik, jadi gak pernah berhenti. PakairunWithOwner, atau buat efeknya di badan komponen. - Latihan:
setCount(1)bukansetCount((c) => c + 1). Klik kedua harus memberiBudi: 2. Kodemu juga harus memuatbatchdan sebuah<button>.
TL;DR: batch(() => { setCount((c) => c + 1); setName("Budi") }) menjalankan efek yang terpengaruh sekali, sesudah semua perubahan; tanpa batch, efeknya jalan dua kali dan sempat melihat Andi: 1. untrack(name) membaca tanpa melacak, dan on(count, (nilai, sebelumnya) => ...) cuma melacak sumber yang disebut (defer: true melewati jalan pertama). Tiap efek punya pemilik; createRoot((dispose) => ...) membuat pemilik yang dibongkar dengan dispose(), dan efek di dalam setTimeout butuh runWithOwner. Untuk daftar besar, createSelector membuat pilihan baru cuma menghitung ulang 2 baris, bukan 1.000, dan createDeferred menunda nilai untuk kerja berat.