Composition API untuk yang Terbiasa Options API

Saya kerja harian dengan Laravel di backend dan Vue di frontend, dan salah satu momen yang bikin ragu waktu pertama kali pindah ke Vue 3 adalah Composition API. Bukan karena sintaksnya susah, tapi karena semua komponen lama saya sudah ditulis rapi pakai Options API — ada data(), ada methods, ada computed, semuanya jelas kotak-kotaknya. Lalu tiba-tiba muncul ref, reactive, setup(), dan pertanyaan paling wajar: apakah saya harus menulis ulang semua komponen? Jawabannya tidak, dan justru itu yang bikin Composition API layak dipelajari pelan-pelan sambil jalan, bukan lewat rewrite besar-besaran.

Tulisan ini saya buat untuk developer yang sudah nyaman dengan Options API dan ingin tahu padanan konsepnya di Composition API, plus cara migrasi bertahap yang realistis untuk proyek yang sudah jalan di produksi.

Memetakan Konsep yang Sudah Anda Kenal

Cara paling cepat memahami Composition API adalah menganggapnya sebagai cara menulis ulang hal yang sama, bukan konsep baru. Di Options API, komponen dipecah berdasarkan jenis properti — semua data di satu blok, semua method di blok lain, semua computed di blok lain lagi. Di Composition API, Anda menulis semuanya sebagai variabel dan fungsi biasa di dalam setup() (atau di dalam <script setup>, yang jadi cara paling umum dipakai sekarang).

Padanannya kira-kira begini:

  • data() jadi ref() untuk nilai primitif (string, number, boolean) atau reactive() untuk objek.
  • methods jadi fungsi biasa yang dideklarasikan di dalam setup().
  • computed jadi computed(), dipakai persis sama — menerima fungsi getter, mengembalikan nilai yang reaktif dan hanya dihitung ulang saat dependensinya berubah.
  • watch tetap watch(), plus tambahan watchEffect() yang otomatis melacak dependensi tanpa Anda sebutkan eksplisit.

Contoh Options API yang sudah familiar:

export default {
  data() {
    return { count: 0, user: { name: '', active: false } }
  },
  computed: {
    label() {
      return this.count > 0 ? `Total: ${this.count}` : 'Belum ada'
    }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}

Versi Composition API-nya, pakai <script setup>:

<script setup>
import { ref, reactive, computed } from 'vue'

const count = ref(0)
const user = reactive({ name: '', active: false })

const label = computed(() =>
  count.value > 0 ? `Total: ${count.value}` : 'Belum ada'
)

function increment() {
  count.value++
}
</script>

Satu hal yang sering bikin bingung di awal: ref membungkus nilai dalam objek, jadi di dalam <script> Anda harus mengaksesnya lewat .value (misalnya count.value++), tapi di dalam template Vue otomatis membuka (unwrap) nilainya, jadi cukup tulis {{ count }} tanpa .value. Ini konsisten dan begitu terbiasa jarang jadi masalah, tapi saya sarankan aturan sederhana: kalau nilainya primitif dan berdiri sendiri, pakai ref; kalau berupa objek dengan banyak properti yang saling terkait, reactive biasanya lebih rapi karena Anda tidak perlu .value di mana-mana.

Kenapa Ini Layak Dipelajari, Bukan Sekadar Sintaks Baru

Alasan utama Composition API diperkenalkan bukan soal gaya penulisan, tapi soal organisasi logika. Di Options API, kalau satu komponen menangani dua fitur berbeda — misalnya pencarian dan paginasi — kode untuk masing-masing fitur tersebar di tiga blok berbeda (data, computed, methods). Semakin besar komponennya, semakin susah melihat "semua yang berhubungan dengan pencarian" dalam satu pandangan, karena harus lompat-lompat antar blok.

Di Composition API, Anda bisa mengelompokkan berdasarkan fitur, bukan jenis properti. State, computed, dan fungsi yang berkaitan dengan pencarian bisa ditulis berdekatan, lalu logika paginasi di bagian lain. Keuntungan yang lebih besar lagi: kelompok logika seperti itu bisa diekstrak jadi fungsi terpisah yang disebut composable — mirip custom hook di React — dan dipakai ulang di komponen lain tanpa lewat mixin, yang di Options API terkenal rawan konflik nama properti.

Migrasi Bertahap: Tidak Perlu Rewrite Sekaligus

Ini bagian yang paling penting untuk proyek yang sudah berjalan. Vue 3 mendukung kedua API sekaligus, bahkan dalam satu komponen. Jadi migrasi yang realistis biasanya begini:

  1. Jangan migrasi komponen yang sudah stabil dan jarang disentuh. Kalau tidak ada alasan bisnis untuk mengubahnya, biarkan tetap Options API. Risiko regresi tidak sebanding dengan manfaat estetika.
  2. Mulai dari komponen baru. Setiap komponen baru yang Anda tulis, tulis dengan <script setup>. Ini cara paling aman untuk membangun kebiasaan tanpa menyentuh kode lama.
  3. Migrasi komponen lama saat Anda memang sedang mengubahnya. Kalau ada bug fix atau fitur baru di komponen Options API yang sudah ada, itu momen wajar untuk sekalian menulis ulang bagian yang disentuh ke Composition API — bukan seluruh file dalam satu commit besar, tapi mengikuti apa yang memang sedang diubah.
  4. Ekstrak logika yang berulang jadi composable lebih dulu. Sebagai ilustrasi, kalau Anda punya pola "ambil data dari API, simpan status loading, tangani error" yang muncul di beberapa komponen, itu kandidat bagus untuk jadi composable seperti useFetch(), terlepas dari komponen pemanggilnya masih Options API atau sudah Composition API.

Satu jebakan yang perlu diwaspadai: campur dua gaya dalam satu file (pakai setup() sebagai opsi di dalam komponen Options API) memang didukung, tapi kalau dipakai berlebihan justru membuat kode lebih susah dibaca, bukan lebih mudah. Saya pribadi lebih suka migrasi per-komponen secara utuh daripada mencampur dua gaya di file yang sama, kecuali untuk kasus transisi yang memang sengaja sementara.

Takeaway

Composition API bukan pengganti yang memaksa Anda membuang semua kode Options API yang sudah jalan. Anggap sebagai alat tambahan: dipakai untuk komponen baru, untuk logika yang perlu dipakai ulang lewat composable, dan untuk komponen lama yang kompleksitasnya sudah mulai sulit dikelola dengan struktur data/methods/computed yang terpisah-pisah. Pemetaan konsepnya sederhana — ref/reactive untuk state, computed() untuk nilai turunan, fungsi biasa untuk method — dan migrasi paling aman selalu berjalan bertahap, mengikuti bagian kode yang memang sedang Anda sentuh.