Cara Meggunakan Vue Refs Di Vue JS
Catatan: Artikel ini mengasumsikan bahwa teman-teman telah memahami dasar-dasar komponen Vue. Jika belum, silahkan baca dokumentasi komponen Vue terlebih dahulu.
Atribut ref
dapat disebut langkah paling akhir yang bisa kita pakai untuk merekayasa DOM bila tidak ada langkah lain. Bahkan juga keterangan atribut ref
berada di bagian Tangani Kasus Sangat jarang pada dokumentasi Vue. Di situ disebut jika kita harus menghindar kecurangan komponen DOM langsung.
Maka saya berharap rekan-rekan memikirkannya dengan masak saat sebelum memakai feature ref
itu. Tidak berarti kita tidak bisa memakainya, di sini saya cuma mengingatkan supaya kita lebih waspada.
Pembukaan
Kita mempunyai komponen Vue seperti berikut:
<template>
<nav>
<input ref="input">
<p>{{ value }}</p>
</nav>
</template>
Komponen itu memiliki input dengan atribut ref
dan sebuah tag paragaraf yang berisi binding data.
Sebutkanlah kita ingin membuat input itu jadi konsentrasi melalui JavaScript. Kita bisa melakukan dengan $refs.input.focus()
.
Masalahnya, kadang kita tidak bisa mengakses property refs yang dimaksud. Karena ref
sendiri dibuat hasil dari dari pemanggilan fungsi render. Selanjutnya tentang keterangan ini, teman-teman bisa membacanya di bagian API Ref - Vue.js.
Saat sebelum meneruskan ke sisi seterusnya, silahkan kita segarkan pengetahuan mengenai ref
dan $refs
terlebih dahulu.
ref
ialah atribut HTML atau props dari sebuah elemen. Nilainya bisa menjadi nama rekomendasi. Dan, $refs
sebagai property instance. Kita bisa mengakses nama rekomendasi yang sudah kita deklarasikan awalnya menggunakan ref
dari property ini.
Menggunakan contoh di atas, kita memberi nilai atribut ref
dengan nilai input
. Maka kita bisa mengaksesnya dengan cara, $refs.input
.
Hal Yang Perlu Dicermati
Kita harus mengetahui di mana dan kapan kita mengakses properti tersebut. Berikut beberapa contoh cara mengakses $refs
dari tempat yang berbeda-beda:
Created
<script>
export default {
data() {
return {
value: ''
}
},
created() {
console.log(this.$refs.input)
// undefined
console.log(this.$refs)
// {input: HTMLInputElement}
}
}
</script>
Mengakses $refs.input
langsung pada created
akan menghasilkan nilai undefined. Sama seperti yang saya deskripsikan di atas, ref
dibuat hasil dari panggilan fungsi render. Dan created
dipanggil saat sebelum fungsi render.
Tapi, hal aneh terjadi bila kita cuma mencetak nilai $refs
. Konsol akan menampilkan property input. Kenapa hal itu bisa terjadi? Dion DiFelice memberi penjelasan ringkas dalam suatu utas StackOverflow, Vue.js refs are undefined, even though this.$refs show they're there:
... is that self.refs is passed by reference. So, by the time you viewed the console, the rest of the data finished loading in a matter of milliseconds. And self.$refs.mapRef is not passed by reference, so it remains undefined, as it was during runtime.
Keterangan itu berlaku bila kita mengakses $refs
pada siklus hidup life cycle Vue yang lainnya terkecuali mounted
dan beforeDestroy
.
Computed
<script>
export default {
computed: {
value() {
return this.$refs.input.value
}
}
}
</script>
Mengakses $refs.input.nilai secara langsung dalam computed properti akan menghasilkan galat (error) seperti berikut:
// TypeError: Cannot read property 'value' of undefined
Hal itu karena property input sendiri bernilai undefined, sehingga kita pun tidak segera dapat mengakses nilai dari property value.
Selain itu, galat (error) berikut ada:
// [Vue warn]: Error in render: "TypeError: Cannot read property 'value' of undefined"
Hal itu karena galat (error) awalnya terjadi saat proses pe-render-an komponen. Sehingga computed properti nilai akan berharga undefined.
Watch
<script>
export default
{ data() {
return {
value: ''
}
},
watch: {
// Tidak terjadi apa-apa
'$refs.input.value': function (currentValue) {
this.value = currentValue
}
}
}
</script>
Memasang watcher perubahan pada input tidak melakukan apa saja, karena $refs
sendiri tidak reaktif. Pada salah satunya sisi Menangani Kasus Sangat jarang, Mengakses Instance Komponen Anak dan Elemen Anak, disebut jika:
$refs hanya diisi setelah komponen telah di-render, dan mereka tidak reaktif.
<script>
export default {
data() {
return {
value: ''
}
},
watch: {
// Tidak terjadi apa-apa
'$refs.input': function (currentInput) {
this.value = currentInput.value
}
}
}
</script>
Walaupun kita hanya memasang watcher pada properti $refs.input, hasilnya juga akan sama saja. Karena properti $refs sendiri tidaklah reaktif.
Methods
<script>
export default {
data() {
return {
value: ''
}
},
methods: {
makeFocus() {
this.$refs.input.focus()
}
}
}
</script>
Mengakses $refs dalam methods cukup susah-susah gampang. Karena hal itu bergantung kapan dan dimana kita memanggil metode itu . Maka kita harus memastikan, kita tidak memanggilnya saat $refs sendiri masih berharga undefined.
Mounted
<script>
export default {
data() {
return {
value: ''
}
},
mounted() {
console.log(this.$refs.input)
// <input></input>
console.log(this.$refs)
// {input: HTMLInputElement}
}
}
</script>
Mungkin lokasi yang paling aman untuk mengakses $refs ialah di mounted. Mengakses $refs.input
dan $refs
akan menghasilkan nilai yang terdefinisi.
Tapi bila kita ingin menambahkan atribut ref
pada komponen, kita harus pastikan komponen itu tidak dimuat secara asinkronus. Meskipun kita mengakses $refs
pada mounted
, hasilnya akan undefined.
<template>
<app-nav ref="nav"></app-nav>
</template>
<script>
export default {
components: {
AppNav: () => import('./components/AppNav')
},
mounted() {
console.log(this.$refs.nav)
// undefined
console.log(this.$refs)
// {}
}
}</script>
$nextTick
<template>
<app-nav ref="nav"></app-nav>
</template>
<script>
export default {
components: {
AppNav: () => import('./components/AppNav')
},
mounted() {
this.$nextTick(() => {
console.log(this.$refs.nav)
// undefined
console.log(this.$refs)
// {}
})
}
}
</script>
Terkadang kita menggunakan $nextTick
untuk menyelesaikan permasalahan untuk mendapatkan nilai dari suatu objek. Tetapi untuk kasus ini, meskipun kita menggunakan $nextTick
secara bertingkat, maka hasilnya tidak akan berhasil. Teman-teman dapat mempelajari lebih lanjut mengenai $nextTick
pada bagian Pembaruan Antrian Async - Vue.js.
setTimeout
Menggunakan setTimeout
akan menunda eksekusi fungsi callback dalam kurun waktu tertentu. Langkah ini akan bekerja bila kita mengetahui berapakah beberapa waktu yang kita perlukan untuk penundaan eksekusi. Penundaan itu tergantung pada sambungan pengguna. Makin lambat internet, makin bertambah juga waktu menunda yang diperlukan.
Katakanlah langkah di atas tidak ada yang dapat diandalkan . Maka, apa yang bisa kita lakukan untuk mengakses nilai $refs
secara aman?
Cara Aman
Saat sebelum meneruskan ulasan, saya ingin bercerita bagaimana saya mendapati jalan keluar dari persoalan itu.
Sekian hari lalu, saya menemukan jika demo interaktif pada artikel "Membuat Sistem Reaktivitas Seperti Vue.js Versi Simpel" baik di bagian 1 dan sisi 2 tidak bekerja. Saya curigai hal itu karena tag yang tidak bisa dilakukan dalam saat production.
Saya mencari tahu langkah lain untuk meng-inject dan jalankan JavaScript sesudah browser (browser) selesai memuat kodenya.
Saya memilih untuk memakai $refs
. Masalahnya, komponen demonstrasi interaktif itu termuat secara asinkronus. Hingga saya harus memakai $nextTick
atau setTimeout
untuk tunda script injection. Sayangnya, kedua cara itu tidak bekerja.
Selanjutnya saya ingat satu hal, saya pernah memakai paket NPM namanya wait-for-expect. Paket itu bermanfaat untuk menunggu Jest expectation bila kita menjalankan code secara asinkronus sampai memperoleh hasil yang sesuai.
Rahasianya ialah paket itu jalankan fungsi callback yang berisi code expectation secara terus-terusan dalam waktu tertentu. Eksekusi itu akan stop saat fungsi callback sudah usai atau mungkin tidak menghasilkan galat (error) apa saja.
Kemungkinan cara itu bisa saya pakai untuk mengakses nilai $refs
tak perlu memperoleh nilai undefined.
<template>
<app-nav ref="nav"></app-nav>
</template>
<script>
export default {
components: {
AppNav: () => import('./components/AppNav')
},
mounted() {
const interval = setInterval(() => {
if (this.$refs.nav) {
console.log(this.$refs.nav) // VueComponent{} console.log(this.$refs) // {nav: VueComponent} clearInterval(interval)
}
}, 50)
}
}
Dimulai dari lokasi yang paling aman untuk mendapatkan nilai $refs
, kita membuat suatu variabel namanya interval
yang direkomendasikan di hasil panggilan fungsi setInterval. Fungsi itu akan menjalankan fungsi callback tiap 50ms.
Kita bisa sesuaikan waktu 50ms itu seperti keinginan kita, tapi di sini saya cuma mengikuti apa yang wait-for-expect
kerjakan.
Dalam fungsi callback, kita lakukan pengujian bila $refs.nav
sudah ada, kita bisa lakukan suatu hal dengan nilainya. Katakanlah seperti mencetaknya pada konsol browser (browser). Selanjutnya, kita panggil fungsi clearInterval
dan masukkan variabel interval
untuk bersihkan memory dan hentikan panggilan fungsi callback.
Dengan itu, kita tak perlu mengetahui berapakah lamanya waktu nantikan yang diperlukan sampai nilai $refs.nav
ada. Selama panggilan fungsi callback sedang berjalan, akan dilaksanakan pengujian pada nilai itu.
Bila rekan-rekan ingin mengetahui bagaimana implikasi cara itu pada demo interaktif.
Kesimpulan
$refs sebagai salah satunya feature Vue.js yang susah-susah gampang untuk digunakan. Kita harus menggunakannya di saat dan tempat yang akurat. Bahkan juga bila kita sudah melakukan, kadang nilai yang dibuat masih undefined.
Lokasi yang paling aman untuk memperoleh nilai $refs ialah di mounted. Selainnya pada tempat itu, akan riskan hasilkan nilai undefined. Hingga, kita harus waspada saat sebelum menggunakannya.
Untuk menangani permasalahan itu, kita bisa memakai fungsi setInterval yang kita panggil di mounted. Dalam fungsi callback-nya, kita lakukan pengujian apa nilai $refs sudah ada atau memang belum. Apabila sudah, karena itu kita bisa lakukan apa saja dengan nilai itu. Dan yang terpenting ialah menghentikan panggilan fungsi callback memakai fungsi clearInterval.