# Vue 3 dengan TypeScript di 2026: Props, Emits, dan Composable Aman-Tipe > Kuasai komponen Vue 3 yang aman-tipe dengan TypeScript: defineProps generik, defineEmits tuple, composable bertipe, defineModel, dan InjectionKey, plus soal wawancara. - Published: 2026-07-07 - Updated: 2026-07-07 - Author: SharpSkill - Tags: Vue 3, TypeScript, defineProps, Composables, Frontend - Reading time: 8 min --- Vue 3 dengan TypeScript pada tahun 2026 memberikan komponen pemeriksaan tipe statis penuh untuk props, event, dan logika yang dapat digunakan ulang. Sintaks ` ``` Prop `role` kini dibatasi pada dua string literal, sehingga meneruskan `role="guest"` akan menggagalkan proses build. Inilah alasan utama untuk memilih `defineProps` dengan TypeScript: [panduan resmi Vue TypeScript](https://vuejs.org/guide/typescript/composition-api.html) memperlakukan bentuk generik sebagai standar untuk proyek ` ``` Vue 3.5 menstabilkan **reactive props destructure**, yang kini menjadi pola yang lebih ringkas. Melakukan destructure pada hasil `defineProps` dan menetapkan nilai bawaan dalam pernyataan yang sama tetap sepenuhnya reaktif — compiler menuliskan ulang setiap akses kembali menjadi `props.x` di balik layar. ```vue ``` > **Jebakan reaktivitas pada props yang di-destructure** > > Props yang di-destructure tetap reaktif di dalam template dan di `computed`, tetapi meneruskan nilai hasil destructure langsung ke `watch` atau ke sebuah composable akan membacanya sekali saja dan memutus tautan reaktifnya. Bungkus dengan getter — `watch(() => color, ...)` — atau konversikan dengan `toRef(props, 'color')` ketika sebuah ref dibutuhkan di bagian selanjutnya. Ini adalah jebakan wawancara yang umum: kandidat mengira variabel hasil destructure adalah nilai biasa, padahal compiler sebenarnya telah mengarahkan ulang setiap pembacaan kembali ke `props.color`. ## Emits aman-tipe dengan defineEmits Event layak mendapat ketelitian yang sama seperti props. `defineEmits` generik mendeskripsikan setiap nama event dan payload-nya sebagai sebuah tuple, memberikan komponen induk pelengkapan otomatis dan komponen anak pemeriksaan pada waktu kompilasi bahwa argumen yang tepat telah dipancarkan. ```vue ``` Bentuk tuple ini menggantikan sintaks tanda tangan panggilan yang lebih lama (`(e: 'search', q: string): void`) karena lebih mudah dibaca dan mendukung banyak event tanpa overload. Ketika sebuah event tidak membawa data, tuple kosong `[]` mendokumentasikannya secara eksplisit. Memadukan emits bertipe dengan props bertipe menghasilkan komponen yang seluruh antarmuka publiknya dapat diverifikasi sebelum runtime — disiplin yang sama seperti yang dibahas dalam [panduan Vue composition API](/blog/vue-nuxt/vue-3-composition-api-complete-guide). ## Mengetik composable untuk logika yang dapat digunakan ulang Composable adalah fungsi biasa, jadi mereka mengikuti aturan TypeScript umum — tetapi beberapa konvensi menjaganya tetap ergonomis. Kembalikan tipe `Ref` secara eksplisit ketika inferensi tidak jelas, dan gunakan generik ketika sebuah composable membungkus data sembarang seperti respons API. ```ts // useFetch.ts import { ref, type Ref } from 'vue' interface UseFetchReturn { data: Ref error: Ref loading: Ref } // Generic flows through to the caller's typed data export function useFetch(url: string): UseFetchReturn { const data = ref(null) as Ref const error = ref(null) const loading = ref(true) fetch(url) .then((r) => r.json()) .then((json: T) => { data.value = json }) .catch((e: Error) => { error.value = e }) .finally(() => { loading.value = false }) return { data, error, loading } } ``` Di titik pemanggilan, parameter generik membuat `data` sepenuhnya bertipe tanpa anotasi tambahan: ```ts // UserList.vue (script setup) interface User { id: number; name: string } // data is Ref — inferred from the generic const { data: users, loading } = useFetch('/api/users') ``` Menganotasi objek yang dikembalikan secara eksplisit (`UseFetchReturn`) sepadan dengan beberapa baris tambahan: hal itu mendokumentasikan kontrak, mencegah kebocoran ref internal yang tidak disengaja, dan memberi konsumen satu tipe untuk diimpor. Untuk pola composable yang lebih mendalam seperti overload argumen dan pembersihan yang sadar-siklus-hidup, lihat [panduan composable Vue tingkat lanjut](/blog/vue-nuxt/advanced-vue-3-composables-reusable-patterns). ## Mengetik defineModel untuk two-way binding `defineModel`, stabil sejak Vue 3.4, menyatukan pasangan prop `modelValue` lama dan emit `update:modelValue` menjadi satu ref yang dapat ditulis. Parameter generiknya mengetik kedua arah binding sekaligus. ```vue ``` Model bernama — `defineModel('title')` — dipetakan ke `v-model:title` dan menerima pengetikan yang sama. Ini menghilangkan seluruh kelas bug payload yang tidak cocok, yang dulu disembunyikan oleh pola prop-dan-emit manual. ## Mengetik template ref dan instance komponen Mengakses sebuah node DOM atau komponen anak melalui sebuah `ref` adalah tempat kode Vue tanpa tipe paling sering jatuh ke `any`. Perbaikannya adalah memberikan parameter tipe elemen pada `useTemplateRef` (Vue 3.5+) atau pada `ref` itu sendiri, sehingga akses properti diperiksa terhadap antarmuka DOM yang sebenarnya. ```vue ``` Untuk referensi ke komponen anak, `InstanceType` mengekstrak tipe publik komponen tersebut, mengekspos apa pun yang dideklarasikan anak melalui `defineExpose`. Ini menjaga panggilan metode induk-ke-anak tetap terperiksa penuh alih-alih ditebak. ```vue ``` ## Provide dan inject aman-tipe dengan InjectionKey Injeksi dependensi melintasi pohon komponen kehilangan informasi tipe kecuali key-nya membawanya. `InjectionKey` adalah sebuah symbol bertipe yang mengikat tipe sebuah nilai ke key-nya, sehingga `provide` dan `inject` tetap selaras tanpa casting manual. ```ts // theme-key.ts import type { InjectionKey, Ref } from 'vue' export interface ThemeContext { mode: Ref<'light' | 'dark'> toggle: () => void } // The key permanently associates the ThemeContext type with this symbol export const ThemeKey: InjectionKey = Symbol('theme') ``` ```ts // provider (script setup) — value must match ThemeContext or it fails to compile provide(ThemeKey, { mode, toggle }) // consumer — theme is inferred as ThemeContext | undefined const theme = inject(ThemeKey) theme?.toggle() ``` Menyediakan sebuah nilai yang bentuknya tidak cocok dengan `ThemeContext` adalah kesalahan kompilasi di titik injeksi, menangkap ketidakcocokan antar komponen yang berjauhan sebelum dirilis. ## Pertanyaan wawancara TypeScript Vue yang umum Pewawancara menyelidiki apakah seorang kandidat memahami batas antara tipe waktu-kompilasi dan perilaku runtime. Beberapa pertanyaan yang sering muncul beserta jawaban tajamnya: - **Mengapa memilih `defineProps()` dibanding bentuk objek runtime?** Props generik dapat mengekspresikan tipe union, tanda tangan fungsi, dan bentuk bersarang yang tidak dapat dilakukan deklarasi runtime, serta menghilangkan duplikasi definisi tipe/runtime. - **Apakah props hasil destructure bersifat reaktif?** Ya pada Vue 3.5+, karena compiler menulis ulang akses menjadi `props.x`. Namun *nilai* hasil destructure yang diteruskan ke `watch` atau ke sebuah composable dibaca sekali saja — gunakan getter atau `toRef`. - **Bagaimana cara mengetik event yang dipancarkan tanpa payload?** Tuple kosong: `defineEmits<{ close: [] }>()`. - **Apa yang digantikan `defineModel`?** Pasangan prop `modelValue` dan emit `update:modelValue`, disatukan menjadi satu ref bertipe yang dapat ditulis. Melatih hal-hal ini dengan bank soal nyata mengasah refleks yang dihargai wawancara — [modul wawancara composable Vue](/technologies/vue-nuxt/interview-questions/vue-composables) melatih pola-pola ini secara persis. Perkakas juga penting: jalankan `vue-tsc` di CI agar kesalahan tipe memblokir merge, dan andalkan [handbook TypeScript](https://www.typescriptlang.org/docs/handbook/2/generics.html) saat generik composable mulai terlibat. Pengalaman editor didukung oleh [perkakas resmi Volar milik Vue](https://github.com/vuejs/language-tools), yang membaca macro-macro ini untuk menampilkan kesalahan secara inline. ## Kesimpulan - Gunakan `defineProps()` generik untuk mengekspresikan tipe union, field opsional, dan prop callback yang tidak dapat ditangkap deklarasi runtime. - Utamakan reactive props destructure dengan nilai bawaan inline pada Vue 3.5+, dan gunakan `withDefaults` hanya ketika objek nilai bawaan bersama lebih jelas. - Ketik event dengan bentuk tuple `defineEmits` agar payload diperiksa pada waktu kompilasi baik di anak maupun di induk. - Anotasi tipe pengembalian composable secara eksplisit dan gunakan generik untuk meneruskan tipe data yang disediakan pemanggil dari ujung ke ujung. - Adopsi `defineModel()` untuk two-way binding guna menyatukan boilerplate prop-plus-emit menjadi satu ref bertipe. - Jalankan `vue-tsc` dalam integrasi berkelanjutan agar regresi tipe menggagalkan build alih-alih mencapai produksi. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/id/blog/vue-nuxt/vue-3-typescript-type-safe-props-emits-composables