Composables — Vue

Composable adalah fungsi yang mengenkapsulasi logika reaktif — mirip custom hooks di React. // useCounter composable function useCounter(initial = 0) { const…

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: IkonKeranjang di header dan HalamanMenu sama-sama memanggil useKeranjang(). Di HalamanMenu, tombol Tambah Nasi Goreng diklik dua kali dan tulisannya jadi "2 item, Rp30000". Ikon di header menampilkan Keranjang (2) atau Keranjang (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

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>

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

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.

Yang akan kamu pelajari