Effects — SolidJS

Effects menjalankan side effect yang otomatis re-run saat dependency berubah. createEffect import { createSignal, createEffect } from "solid-js" function…

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 Buku diklik dua kali. Isi localStorage untuk "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"

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>
  )
}

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 }))

💡 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

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.

Yang akan kamu pelajari