Composable adalah fungsi biasa yang namanya diawali use, berisi data reaktif dan fungsi-fungsi yang mengubahnya, supaya logika yang sama bisa dipakai di banyak komponen. Bayangin kartu resep "cara mencatat pesanan" yang ditempel Bu Sri di dapur. Setiap kasir baru mengikuti kartu yang sama, dan masing-masing mencatat di buku pesanannya sendiri. Kartu resepnya composable, buku pesanannya state reaktif, dan setiap kasir yang mengikuti kartu itu adalah komponen yang memanggilnya.
🧪 Tebakan cepat:
IkonKeranjangdi header danHalamanMenusama-sama memanggiluseKeranjang(). DiHalamanMenu, tombol Tambah Nasi Goreng diklik dua kali dan tulisannya jadi "2 item, Rp30000". Ikon di header menampilkanKeranjang (2)atauKeranjang (0)? Tebak dulu, jawabannya ada di bagian "Setiap panggilan punya state sendiri".
Composable: logika keranjang dalam satu fungsi
// src/composables/useKeranjang.js
import { ref } from "vue"
export function useKeranjang() {
const jumlah = ref(0)
const total = ref(0)
function tambah(harga) {
jumlah.value++
total.value += harga
}
return { jumlah, total, tambah }
}
<!-- HalamanMenu.vue -->
<script setup>
import { useKeranjang } from "@/composables/useKeranjang"
const { jumlah, total, tambah } = useKeranjang()
</script>
<template>
<button @click="tambah(15000)">Tambah Nasi Goreng</button>
<p>Keranjang: {{ jumlah }} item, Rp{{ total }}</p>
</template>
| Klik | jumlah |
total |
Layar |
|---|---|---|---|
| awal | 0 | 0 | Keranjang: 0 item, Rp0 |
| Tambah Nasi Goreng | 1 | 15000 | Keranjang: 1 item, Rp15000 |
| Tambah Nasi Goreng | 2 | 30000 | Keranjang: 2 item, Rp30000 |
- Namanya diawali
use:useKeranjang,useCounter,useToggle. Itu konvensi, bukan aturan JavaScript, tapi semua orang Vue membacanya sebagai "ini composable". - Di dalamnya boleh semua Composition API:
ref,reactive,computed,watch,onMounted. - Dia mengembalikan ref-nya (
jumlah, bukanjumlah.value) dan fungsi pengubahnya. Karena tiap ref adalah kotak, membongkarnya denganconst { jumlah, total, tambah } = ...tetap reaktif. - Composable dipanggil di
<script setup>(atau di composable lain), langsung saat komponen dibuat. Watcher dan lifecycle hook di dalamnya lalu menempel ke komponen yang memanggilnya. - Di proyek sungguhan dia tinggal di file sendiri, biasanya di folder
src/composables/, dan di-export.
Di editor latihan
Editor gak punya file lain, jadi composable ditulis sebagai fungsi di bagian script, tanpa export dan import:
function useCounter(initial = 0) {
const count = ref(initial)
const increment = () => count.value++
const reset = () => count.value = initial
return { count, increment, reset }
}
const { count, increment, reset } = useCounter(0)
---template---
<div>
<span id="count">{{ count }}</span>
<button id="inc" @click="increment">+1</button>
<button id="reset" @click="reset">Reset</button>
</div>
initial = 0adalah nilai bawaan parameter:useCounter()mulai dari 0,useCounter(5)dari 5.- Urutannya: awal
0, klik +1 jadi1, klik lagi2, Reset kembali0.incrementharus menambah (count.value++), bukan mengisi 1. - Latihan kedua sama bentuknya:
useToggle(initial = false)menyimpanisOn = ref(initial),togglemembaliknya denganisOn.value = !isOn.value, lalu dipakai sebagaiuseToggle(true). Template-nya{{ isOn ? "ON" : "OFF" }}: awalON, sekali klikOFF, dua kaliONlagi. - Bentuk arrow juga boleh:
const useToggle = (initial = false) => { ... }.
Kembalikan ref, bukan isinya
return { jumlah, total, tambah } // benar: ref
return { ...toRefs(keranjang), tambah } // benar: toRefs dari object reactive
return { ...keranjang, tambah } // salah: salinan angka
return data.value // salah: isi saat itu saja
| Yang dikembalikan | Setelah dibongkar | Klik Tambah sekali |
|---|---|---|
{ jumlah, total, tambah } |
ref | layar 1 item, Rp15000 |
{ ...toRefs(keranjang), tambah } |
ref yang tersambung ke keranjang |
layar 1 item, Rp15000 |
{ ...keranjang, tambah } |
angka 0 biasa | layar tetap 0 item, Rp0 |
{ ...keranjang } menyalin isi object reactive saat itu, jadi yang sampai ke komponen cuma angka 0. tambah tetap mengubah keranjang di dalam, tapi layarnya gak tahu. Sama halnya return data.value: kalau data masih null, yang dikembalikan null, dan data yang datang belakangan gak pernah sampai ke layar. Kembalikan data (ref-nya) atau { data }.
Setiap panggilan punya state sendiri
jumlah dan total dibuat di dalam useKeranjang, jadi setiap panggilan membuat ref baru. IkonKeranjang dan HalamanMenu masing-masing punya keranjang sendiri. Setelah dua klik di HalamanMenu, ikon di header tetap Keranjang (0). Itu jawaban tebakannya.
Kalau keranjangnya memang harus satu untuk semua komponen, buat ref-nya di luar fungsi:
const jumlah = ref(0) // dibuat sekali, waktu file-nya dimuat
const total = ref(0)
export function useKeranjang() {
function tambah(harga) {
jumlah.value++
total.value += harga
}
return { jumlah, total, tambah }
}
Sekarang semua pemanggil memegang ref yang sama, dan ikon di header ikut jadi Keranjang (2). Untuk state yang dibagi seluruh aplikasi, alat standarnya Pinia, yang dibahas di pelajaran Pinia nanti.
📌 Composable dan mixin
Vue 2 berbagi logika lewat mixin: object berisi data dan methods yang dilebur diam-diam ke komponen. Masalahnya, dari template gak kelihatan jumlah datang dari mixin mana, dan dua mixin yang sama-sama punya jumlah saling menimpa. Composable menyelesaikan keduanya: asalnya terlihat di baris const { jumlah } = useKeranjang(), dan nama yang bentrok bisa diganti waktu dibongkar, misalnya const { jumlah: jumlahMeja } = useKeranjang(). Composable juga bisa menerima parameter, seperti useCounter(5).
⚠️ Jebakan yang sering ketemu
- Mengembalikan
.value.return data.valuememutus reaktivitas. Kembalikan ref-nya. - Membongkar object
reactive.return { ...keranjang }mengirim salinan. PakaitoRefs(keranjang), atau pakairefdari awal. - Memanggil composable di event handler atau
setTimeout. Di situ gak ada komponen yang sedang dibuat.onMounteddi dalamnya gak pernah jalan, dan build development menulis[Vue warn]: onMounted is called when there is no active component instance to be associated with.Panggil di<script setup>. - Nama fungsi beda dari brief. Latihan memeriksa ada
function useCounteratauuseCounter =(danuseToggledi latihan kedua), bahwa komponennya memanggilnya (= useCounter(), lalu menekan tombolnya dua kali dari halaman baru: hasilnya harus2, danONlagi untuk toggle.
TL;DR: Composable adalah fungsi useX yang membungkus state reaktif dan fungsi pengubahnya, misalnya useKeranjang() yang mengembalikan { jumlah, total, tambah }. Kembalikan ref, bukan .value, supaya pembongkaran const { jumlah } = useKeranjang() tetap reaktif; object reactive dikembalikan lewat toRefs. Panggil di <script setup>. State yang dibuat di dalam fungsi baru untuk setiap pemanggil; state di luar fungsi dibagi semua pemanggil. Di editor latihan, composable ditulis sebagai fungsi biasa di bagian script, tanpa export dan import.