Watcher adalah fungsi yang dijalankan Vue setiap kali data reaktif tertentu berubah. Dia dipakai untuk efek samping (side effect): pekerjaan di luar menghitung nilai untuk ditampilkan, misalnya menyimpan keranjang ke localStorage, menulis ke console, atau memanggil server. Bayangin Bu Sri berpesan ke pelayan: "Setiap kali angka pesanan di papan berubah, salin angkanya ke buku catatan." Pelayan gak menghitung apa-apa. Dia cuma menunggu papannya berubah, lalu bertindak. Papan itu data reaktif, pelayannya watcher.
🧪 Tebakan cepat:
keranjang = reactive({ menu: "Es Teh", jumlah: 1 }), laluwatch(keranjang.jumlah, (baru) => localStorage.setItem("jumlah", baru)). Pembeli menekan +1 dua kali, layarnya jadi 3. Apa isilocalStorageuntuk"jumlah"? Tebak dulu, jawabannya ada di bagian "Sumber yang bisa diawasi".
watch(): jalan saat sumbernya berubah
const jumlah = ref(Number(localStorage.getItem("jumlah") ?? 1))
watch(jumlah, (baru, lama) => {
localStorage.setItem("jumlah", baru)
console.log("Disimpan:", lama, "→", baru)
})
---template---
<p>Es Teh × {{ jumlah }} · Rp{{ jumlah * 5000 }}</p>
<button @click="jumlah++">+1 porsi</button>
| Kejadian | jumlah |
Console | localStorage "jumlah" |
|---|---|---|---|
| Halaman tampil | 1 | (kosong) | (belum ada) |
| Klik +1 porsi | 2 | Disimpan: 1 → 2 |
"2" |
| Klik +1 porsi | 3 | Disimpan: 2 → 3 |
"3" |
| Muat ulang halaman | 3 | (kosong) | "3" |
watch(sumber, callback): argumen pertama data yang diawasi, argumen kedua fungsi yang dijalankan setiap sumbernya berubah.- Callback menerima nilai baru lalu nilai lama. Namanya bebas:
(baru, lama)atau(newVal, oldVal). watchitu lazy: waktu halaman pertama tampil dia diam. Dia baru jalan saatjumlahbenar-benar berubah. Mengisi nilai yang sama (jumlah.value = 3saat sudah 3) juga gak memicunya.localStoragemenyimpan teks, jadi angka 2 tersimpan sebagai"2". Karena itu nilai awalnya dibaca denganNumber(...), dan?? 1dipakai kalau belum ada yang tersimpan. Berkat watcher ini, muat ulang halaman tetap 3 porsi.- Di proyek sungguhan, import dulu:
import { ref, watch } from "vue". Di editor latihan semuanya sudah tersedia.
Latihan pertama memakai pola yang sama: watch(count, (newVal) => { console.log("Count:", newVal) }). Klik tombolnya sekali, angka jadi 1 dan console menulis Count: 1. Latihan kedua mengawasi status dari sebuah <select> dan memakai nilai lamanya: ganti dari Buka ke Tutup, paragrafnya jadi Buka ke Tutup.
watch atau computed?
computed |
watch |
|
|---|---|---|
| Gunanya | menghitung nilai untuk ditampilkan | melakukan sesuatu saat data berubah |
| Mengembalikan | nilai (ref yang cuma bisa dibaca) | gak mengembalikan nilai untuk template |
| Contoh warung | total = computed(() => jumlah.value * 5000) |
menyimpan jumlah ke localStorage |
Pegangannya: kalau hasilnya mau ditampilkan, pakai computed. Kalau mau melakukan sesuatu, pakai watcher. Teks seperti "Mencari: " + query adalah nilai turunan dan cocok jadi computed. Teks yang butuh nilai lama, seperti Buka ke Tutup di latihan kedua, gak bisa dibuat computed: computed cuma tahu nilai sekarang, sedangkan callback watch menerima (baru, lama).
watchEffect(): jalan langsung, sumbernya dicatat sendiri
watchEffect(() => {
localStorage.setItem("jumlah", jumlah.value)
console.log("Disimpan:", jumlah.value)
})
watchEffectcuma menerima satu fungsi. Gak ada sumber yang ditulis: Vue mencatat sendiri data reaktif apa saja yang dibaca di dalamnya, di sinijumlah.- Dia jalan langsung sekali waktu dibuat, lalu jalan lagi setiap data yang dibacanya berubah. Halaman tampil: console langsung menulis
Disimpan: 1. - Gak ada nilai lama. Kalau butuh angka sebelumnya, pakai
watch.
watch |
watchEffect |
|
|---|---|---|
| Sumber | ditulis eksplisit | dicatat otomatis dari yang dibaca |
| Jalan pertama kali | saat sumbernya berubah | langsung, waktu dibuat |
| Nilai lama | ada | gak ada |
watch juga bisa jalan langsung dengan opsi { immediate: true }. Panggilan pertamanya menerima lama = undefined.
Sumber yang bisa diawasi
const keranjang = reactive({ menu: "Es Teh", jumlah: 1 })
watch(keranjang.jumlah, simpan) // salah: yang dikirim angka 1
watch(() => keranjang.jumlah, simpan) // benar: getter
watch(keranjang, simpan) // seluruh object, deep
| Sumber | Contoh | Watcher jalan saat |
|---|---|---|
| ref | jumlah |
jumlah.value berubah |
| getter | () => keranjang.jumlah |
hasil fungsinya berubah |
object reactive |
keranjang |
properti apa pun di dalamnya berubah, sedalam apa pun |
| array sumber | [menu, jumlah] |
salah satunya berubah |
keranjang.jumlah dibaca JavaScript sebelum watch dipanggil, jadi yang sampai ke watch cuma angka 1, bukan data reaktif. Angka gak bisa berubah, jadi watcher-nya gak pernah jalan, dan localStorage tetap kosong walau layarnya sudah 3. Itu jawaban tebakannya. Di build production gak ada pesan apa pun; build development menulis [Vue warn]: Invalid watch source: 1 A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types. Bungkus jadi getter: () => keranjang.jumlah.
Mengawasi seluruh keranjang itu deep otomatis: mengganti menu pun memicunya. Tapi baru dan lama adalah object yang sama, karena isinya diubah di tempat. Setelah jumlah naik dari 1 ke 2, lama.jumlah juga 2. Kalau butuh angka lamanya, awasi getter-nya.
📌 Opsi dan waktu jalannya
{ immediate: true }: jalan sekali waktu dibuat, sepertiwatchEffect.{ once: true }(Vue 3.4+): jalan sekali saja, lalu berhenti.{ deep: true }: untuk ref yang berisi object.watch(ref({ jumlah: 1 }), ...)tanpadeepcuma jalan kalau object-nya diganti, bukan kalaujumlahdi dalamnya berubah.{ flush: "post" }: secara bawaan callback jalan sebelum Vue memperbarui DOM. Membaca<p id="j">di dalamnya masih mendapat angka lama,1. Denganflush: "post", yang terbaca sudah2.watchdanwatchEffectmengembalikan fungsi penghenti:const berhenti = watch(...), laluberhenti(). Watcher yang dibuat di setup komponen otomatis berhenti saat komponennya dilepas.- Callback juga menerima argumen ketiga,
onCleanup, untuk membereskan pekerjaan yang belum selesai sebelum callback berikutnya jalan. Pelajaran Fetching Data memakainya untuk request yang kedaluwarsa.
⚠️ Jebakan yang sering ketemu
- Mengawasi properti tanpa getter.
watch(keranjang.jumlah, ...)gak pernah jalan. Tuliswatch(() => keranjang.jumlah, ...). - Mengira
watchjalan di awal. Dia lazy. Kalau perlu jalan sejak halaman tampil, beri{ immediate: true }atau pakaiwatchEffect. - Membaca ulang sumbernya di luar callback. Teks yang disusun sekali di luar callback gak ikut berubah. Susun teksnya di dalam callback, dari nilai baru yang diterima.
- Memakai watch untuk nilai yang bisa dihitung. Kalau hasilnya cuma untuk ditampilkan dari nilai sekarang, computed lebih ringkas. Watcher dipakai untuk efek samping, seperti log di latihan pertama, atau untuk sesuatu yang butuh nilai lama, seperti latihan kedua.
TL;DR: Watcher menjalankan efek samping saat data reaktif berubah, sedangkan computed menghitung nilai untuk ditampilkan. watch(jumlah, (baru, lama) => { ... }) butuh sumber eksplisit, lazy (gak jalan di awal), dan memberi nilai baru dan lama. watchEffect(() => { ... }) mencatat sendiri data yang dibacanya dan langsung jalan sekali. Sumber yang sah: ref, getter () => keranjang.jumlah, object reactive (deep, tapi lama sama dengan baru), atau array dari itu. keranjang.jumlah tanpa getter cuma angka, jadi watcher-nya gak pernah jalan. Opsi penting: immediate, deep, once, dan flush: "post" untuk membaca DOM yang sudah diperbarui.