Jebakan Reaktivitas Vue yang Sering Bikin Bingung
Kalau sudah lama pakai Vue, saya yakin hampir semua orang pernah mengalami momen ini: mengubah sebuah variabel, tapi tampilan di layar diam saja seperti tidak terjadi apa-apa. Bukan karena Vue rusak, tapi karena reaktivitas Vue bekerja dengan cara yang sangat spesifik, dan begitu kita keluar dari pola yang diharapkan, koneksi antara data dan tampilan bisa putus tanpa error apa pun yang muncul di console. Ini salah satu sumber kebingungan paling umum buat developer yang baru pindah dari jQuery atau vanilla JS ke Vue, dan bahkan developer yang sudah berpengalaman pun kadang masih kena jebakan ini kalau sedang terburu-buru. Di tulisan ini saya mau bahas dua kasus yang menurut saya paling sering muncul: mengganti array atau object secara langsung tanpa cara yang reaktif, dan destructuring yang tanpa sadar menghilangkan reaktivitas.
Mengganti Array atau Object Langsung Tanpa Reaktif
Inti dari sistem reaktivitas Vue (baik di Vue 2 dengan Object.defineProperty maupun Vue 3 dengan Proxy) adalah: Vue perlu tahu kapan sebuah properti dibaca dan kapan properti itu diubah. Masalahnya, tidak semua cara mengubah data itu terlihat oleh Vue.
Contoh klasik di Vue 2: mengubah index array secara langsung atau menambah properti baru ke object yang sudah ada.
// Vue 2, ini TIDAK reaktif
this.items[0] = 'baru';
this.user.email = 'baru@contoh.com'; // properti baru di object lama
Di Vue 2, perubahan seperti ini tidak terdeteksi karena Vue hanya mendaftarkan getter/setter untuk properti yang sudah ada saat object diinisialisasi. Solusinya waktu itu memakai Vue.set() atau method array bawaan seperti splice yang memang sudah di-wrap Vue supaya reaktif.
Di Vue 3 dengan Composition API dan ref/reactive, masalah ini jauh lebih jarang karena Proxy bisa mendeteksi penambahan properti baru. Tapi bukan berarti jebakannya hilang total. Yang paling sering saya lihat sekarang justru soal mengganti seluruh isi array dengan cara yang salah, misalnya begini:
const items = ref([1, 2, 3]);
// ini salah kalau tujuannya reset isi array,
// karena .value ditimpa referensi baru,
// tapi kalau ada bagian lain yang sudah
// memegang referensi lama, itu jadi tidak sinkron
items.value = [4, 5, 6]; // sebenarnya ini valid untuk ref,
// tapi kalau 'items' di-destructure dari reactive object
// tanpa toRef, ini justru gagal total
Aturan praktis yang saya pegang: kalau mau mengubah isi array, pakai method yang memang memutasi array itu sendiri seperti push, splice, atau sort, atau timpa seluruh .value dari ref-nya langsung, bukan salah satu elemen di dalamnya secara paksa lewat index tanpa Vue.set di Vue 2. Kalau ragu apakah operasi tertentu reaktif, cara paling aman adalah cek dokumentasi resmi Vue tentang caveat reaktivitas array, karena daftarnya cukup spesifik dan berubah antar versi.
Destructuring yang Menghilangkan Reaktivitas
Ini jebakan yang menurut saya lebih berbahaya karena tidak terasa seperti kesalahan sama sekali, apalagi kalau kita terbiasa dengan destructuring di JavaScript modern. Bayangkan kita punya reactive object dan mau ambil satu propertinya untuk dipakai di komponen:
const state = reactive({ count: 0, name: 'Puja' });
// ini memutus reaktivitas!
const { count, name } = state;
// 'count' di sini cuma nilai primitif biasa,
// bukan lagi terhubung ke 'state.count'
Kenapa ini terjadi? Karena reactive() di Vue 3 mengembalikan Proxy yang membungkus object aslinya, dan reaktivitas itu menempel pada property access lewat proxy tersebut, bukan pada nilai itu sendiri. Begitu kita destructuring, yang kita ambil adalah nilai mentahnya di momen itu saja, referensinya ke proxy sudah putus. Tipe data primitif seperti number dan string memang tidak bisa dibungkus jadi reaktif dengan sendirinya, jadi begitu keluar dari proxy, ya sudah, jadi variabel biasa.
Solusi resminya adalah toRefs() atau toRef(), yang mengubah setiap properti jadi ref tersendiri yang tetap terhubung ke object aslinya:
const state = reactive({ count: 0, name: 'Puja' });
const { count, name } = toRefs(state);
// sekarang count dan name tetap reaktif,
// tapi harus diakses lewat .value di script
count.value++;
Pola ini sangat umum muncul saat kita memakai composable, semacam custom hook di Vue. Kalau sebuah composable mengembalikan reactive object langsung lalu dipakai dengan destructuring di komponen pemanggilnya, reaktivitas bisa hilang begitu saja tanpa error, dan komponen cuma tidak pernah re-render meski data di composable berubah. Karena itu, konvensi umum di ekosistem Vue adalah composable sebaiknya mengembalikan object berisi ref individual (atau hasil toRefs()), bukan satu reactive object mentah, justru supaya aman kalau nanti di-destructure oleh pemanggilnya.
Kenapa Ini Penting Dipahami, Bukan Sekadar Dihafal
Saya pribadi lebih suka memahami kenapa Vue bekerja seperti ini daripada sekadar menghafal daftar "jangan lakukan ini, jangan lakukan itu". Begitu paham bahwa reaktivitas Vue 3 itu berbasis Proxy yang menempel pada object, dan primitif tidak bisa reaktif dengan sendirinya, kita jadi bisa menebak sendiri operasi mana yang aman dan mana yang berisiko, bahkan untuk kasus yang belum pernah kita temui sebelumnya. Ini juga membantu waktu membaca kode orang lain atau melakukan code review, karena pola-pola berisiko seperti destructuring langsung dari reactive() jadi lebih mudah dikenali sekilas.
Sebagai ilustrasi, kalau tim saya sedang review pull request dan menemukan destructuring dari hasil reactive() atau dari return value composable tanpa toRefs, itu biasanya jadi salah satu hal pertama yang saya tanyakan, karena risikonya bug yang sangat halus dan susah dilacak lewat testing biasa.
Takeaway
Dua aturan sederhana yang bisa dipegang: pertama, ubah array dan object lewat method atau API yang memang dirancang reaktif (method mutasi array, Vue.set di Vue 2 bila masih relevan, atau timpa .value pada ref), jangan mengandalkan penulisan index atau properti secara langsung tanpa memahami batasannya. Kedua, kalau perlu destructuring dari reactive() object, selalu lewat toRefs() atau toRef() supaya koneksi ke data aslinya tidak putus. Begitu dua hal ini jadi kebiasaan, sebagian besar bug "kenapa tampilan tidak update" di Vue biasanya hilang dengan sendirinya.