Custom primitive adalah fungsi biasa yang kamu tulis sendiri: di dalamnya dia membuat signal (kadang juga efek dan onCleanup), lalu mengembalikan pembaca nilainya beserta cara mengubahnya. Namanya diawali create, seperti createSignal milik Solid. Dengan begitu logika yang sama bisa dipakai banyak komponen tanpa ditulis ulang. Bayangin cetakan kartu stok di Toko Buku Kak Nadia. Tiap rak memakai cetakan yang sama, tapi tiap kartu yang dicetak mencatat angkanya sendiri.
🧪 Tebakan cepat: kartu Laskar Pelangi dan kartu Bumi Manusia sama-sama memanggil
createCounter(0)untuk jumlah pesanannya. Kamu menekan + dua kali di Laskar Pelangi. Jumlah di Bumi Manusia jadi berapa? Jawabannya ada di bagian "Tiap panggilan, signal baru".
createCounter: logika yang dipakai ulang
import { createSignal } from "solid-js"
function createCounter(initial = 0) {
const [count, setCount] = createSignal(initial)
return {
count,
increment: () => setCount((c) => c + 1),
decrement: () => setCount((c) => c - 1),
reset: () => setCount(initial),
}
}
function App() {
const counter = createCounter(0)
return (
<div>
<p>Count: {counter.count()}</p>
<button onClick={counter.increment}>+</button>
<button onClick={counter.decrement}>-</button>
<button onClick={counter.reset}>Reset</button>
</div>
)
}
Mulai dari Count: 0, klik |
Paragraf |
|---|---|
+ |
Count: 1 |
- |
Count: -1 |
+, lalu Reset |
Count: 0 |
createCountermembuat signal-nya sendiri dengancreateSignal, lalu mengembalikan objek berisicount(pembacanya) dan tiga fungsi pengubah:increment,decrement,reset. Ini latihan pertama, dengan nama-nama itu persis.- Komponennya gak menyentuh
setCount. Dia cuma membacacounter.count()dan memberikancounter.incrementkeonClick. - Yang dikembalikan
count, fungsinya, bukancount(), nilainya. Pembacanya harus tetap fungsi supaya JSX bisa membacanya ulang.
Tiap panggilan, signal baru
function KartuBuku(props) {
const jumlah = createCounter(0)
return (
<div class="kartu">
<h3>{props.judul}</h3>
<p>Jumlah: {jumlah.count()}</p>
<button onClick={jumlah.increment}>+</button>
</div>
)
}
Dua klik + di Laskar Pelangi, dan Bumi Manusia tetap 0. Itu jawaban tebakannya. Tiap KartuBuku memanggil createCounter(0) sendiri, dan tiap panggilan menjalankan createSignal baru. Kalau createSignal ditulis di luar fungsinya, semua kartu berbagi satu signal: klik di Laskar Pelangi ikut mengubah Bumi Manusia.
createToggle: mengembalikan pasangan
Latihan kedua mengembalikan array, seperti createSignal, supaya pemakainya bebas memberi nama:
function createToggle(initial = false) {
const [value, setValue] = createSignal(initial)
const toggle = () => setValue((v) => !v)
return [value, toggle]
}
function App() {
const [visible, toggle] = createToggle(false)
return (
<div>
<p>{visible() ? "Visible" : "Hidden"}</p>
<button onClick={toggle}>Toggle</button>
</div>
)
}
Awalnya Hidden, sekali klik Visible, dua kali klik Hidden lagi. setValue((v) => !v) membalik nilai lamanya.
createDebounced: primitive yang membersihkan dirinya
Kotak cari di toko menyaring katalog. Menyaring tiap huruf itu boros, jadi tunggu pengetiknya berhenti 300 ms:
import { createSignal, createEffect, onCleanup } from "solid-js"
function createDebounced(sumber, jeda) {
const [nilai, setNilai] = createSignal(sumber())
createEffect(() => {
const baru = sumber()
const id = setTimeout(() => setNilai(baru), jeda)
onCleanup(() => clearTimeout(id))
})
return nilai
}
// di komponen Pencarian:
const [cari, setCari] = createSignal("")
const cariPelan = createDebounced(cari, 300)
Diuji di Solid 1.9, mengetik bumi satu huruf tiap 100 ms:
createDebounced |
Filter yang membaca cariPelan() jalan |
|---|---|
dengan onCleanup |
1 kali, dengan "bumi", 300 ms sesudah huruf terakhir |
tanpa onCleanup |
4 kali: "b", "bu", "bum", "bumi" |
onCleanupdi dalam efek jalan sebelum efeknya jalan lagi, jadi tiap huruf baru membatalkan timer huruf sebelumnya.- Dia juga jalan saat komponen pemakainya dibongkar. Diuji: tanpa
onCleanup, tiga timer masih jalan sesudah pencariannya ditutup.
createLocalStorage: nilai yang bertahan
function createLocalStorage(kunci, awal) {
const tersimpan = localStorage.getItem(kunci)
const [nilai, setNilai] = createSignal(tersimpan !== null ? JSON.parse(tersimpan) : awal)
createEffect(() => localStorage.setItem(kunci, JSON.stringify(nilai())))
return [nilai, setNilai]
}
const [keranjang, setKeranjang] = createLocalStorage("keranjang", [])
Tiap kali keranjang berubah, efeknya menyimpan salinan JSON ke browser. Saat halaman dibuka lagi, nilai awalnya dibaca dari sana, jadi isi keranjang gak hilang.
Aturan menulis primitive
- Nama diawali
create:createCounter,createToggle,createDebounced. Ini konvensi Solid. Sebagian orang memakaiuse, tapicreateyang umum di Solid. - Panggil di badan komponen, bukan di dalam
onClick. Efek di dalam primitive butuh pemilik, sama seperti di pelajaran Reactivity Lanjutan. - Kembalikan pembaca (fungsi), bukan nilainya.
- Matikan dengan
onCleanupapa pun yang dinyalakan: timer, listener, koneksi. - Untungnya: logika reusable (dipakai ulang di banyak komponen) dan testable (bisa diuji tanpa tampilan, dibahas di pelajaran Testing).
⚠️ Jebakan yang sering ketemu
return [value(), toggle]. Yang dikembalikanfalse, bukan fungsi, jadivisible()diAppberhenti denganTypeError: visible is not a function. Kembalikanvalue.createSignaldi luar fungsi primitive. Semua pemakainya berbagi satu nilai.- Lupa
onCleanup. Timer lama tetap jalan, filternya jalan tiap huruf, dan kerja tetap terjadi sesudah komponennya hilang. - Latihan 1: tombolnya bertulisan persis
+,-, danReset, paragrafnya mulai dariCount: 0, dan kodemu memuatcreateCounter,increment,decrement, danreset.
TL;DR: Custom primitive adalah fungsi createXxx yang membuat signal di dalamnya dan mengembalikan pembacanya plus cara mengubahnya: createCounter(0) memberi { count, increment, decrement, reset }, createToggle(false) memberi [value, toggle]. Tiap panggilan membuat signal baru, jadi dua kartu gak saling ganggu. Kembalikan fungsinya (value), bukan nilainya (value()). Primitive yang menyalakan sesuatu, seperti timer di createDebounced, mematikannya dengan onCleanup: tiap huruf baru membatalkan timer lama, dan gak ada timer yang tersisa saat komponennya dibongkar. Keuntungannya: reusable dan testable.