Vue 3 Script Setup dan defineModel di 2026: Sintaks Modern dan Pertanyaan Interview
Panduan lengkap tentang sintaks script setup Vue 3, defineModel untuk two-way binding, reactive props destructure di Vue 3.5, dan pertanyaan interview yang sering diajukan.

Sintaks script setup Vue 3 mengubah cara penulisan komponen dengan menghilangkan boilerplate dan meningkatkan integrasi TypeScript. Dikombinasikan dengan defineModel, yang diperkenalkan di Vue 3.3 sebagai fitur eksperimental dan distabilkan di Vue 3.4, pembuatan komponen dengan two-way data binding membutuhkan kode yang jauh lebih sedikit dibandingkan dengan Options API.
Mulai dari Vue 3.5, props dapat di-destructure langsung dari defineProps dengan tetap mempertahankan reaktivitas. Ini menghilangkan kebutuhan untuk toRefs dalam kebanyakan kasus.
Dasar-Dasar Script Setup di Vue 3.5
Sintaks <script setup> adalah compile-time sugar untuk Composition API di dalam Single File Components. Variabel yang dideklarasikan di level atas secara otomatis terekspos ke template, dan import dapat langsung digunakan tanpa registrasi.
<!-- UserCard.vue -->
<script setup lang="ts">
import { ref, computed } from 'vue'
import type { User } from '@/types'
// Props dengan TypeScript interface
const props = defineProps<{
user: User
showEmail?: boolean
}>()
// Reactive state
const isExpanded = ref(false)
// Computed property
const displayName = computed(() => {
return `${props.user.firstName} ${props.user.lastName}`
})
// Event emitter dengan typed payload
const emit = defineEmits<{
select: [userId: string]
delete: [userId: string]
}>()
// Methods adalah plain functions
function handleSelect() {
emit('select', props.user.id)
}
</script>
<template>
<div class="user-card" @click="handleSelect">
<h3>{{ displayName }}</h3>
<p v-if="showEmail">{{ user.email }}</p>
<button @click.stop="isExpanded = !isExpanded">
{{ isExpanded ? 'Collapse' : 'Expand' }}
</button>
</div>
</template>Compiler menangani export statements dan registrasi komponen secara otomatis. Ini mengurangi beban kognitif dibandingkan dengan mengembalikan nilai secara eksplisit dari fungsi setup().
Reactive Props Destructure (Vue 3.5+)
Sebelum Vue 3.5, destructuring props merusak reaktivitas karena destructuring JavaScript membuat snapshot dari nilai pada saat itu. Vue 3.5 memperkenalkan tracking di level compiler yang mempertahankan reaktivitas untuk props yang di-destructure.
<!-- SearchInput.vue -->
<script setup lang="ts">
import { watch } from 'vue'
// Destructure dengan defaults - tetap reaktif di 3.5+
const { query = '', placeholder = 'Search...' } = defineProps<{
query?: string
placeholder?: string
}>()
// Watch berfungsi pada props yang di-destructure
watch(
() => query,
(newQuery) => {
console.log('Query changed:', newQuery)
}
)
</script>
<template>
<input
:value="query"
:placeholder="placeholder"
@input="$emit('update:query', ($event.target as HTMLInputElement).value)"
/>
</template>Sintaks ini membutuhkan Vue 3.5 atau lebih tinggi. Dokumentasi Vue 3.5 release announcement mencatat daftar lengkap perilaku reactive props destructure.
defineModel untuk Two-Way Binding
Sebelum defineModel, implementasi v-model pada komponen kustom memerlukan deklarasi prop dan emit update event secara manual. Pola ini muncul di banyak codebase Vue:
<!-- Cara lama - verbose tapi eksplisit -->
<script setup lang="ts">
const props = defineProps<{ modelValue: string }>()
const emit = defineEmits<{ 'update:modelValue': [value: string] }>()
function updateValue(event: Event) {
emit('update:modelValue', (event.target as HTMLInputElement).value)
}
</script>Dengan defineModel, komponen yang sama menjadi jauh lebih singkat:
<!-- TextInput.vue -->
<script setup lang="ts">
// defineModel membuat ref yang sync dengan v-model
const model = defineModel<string>({ default: '' })
</script>
<template>
<input
v-model="model"
type="text"
class="text-input"
/>
</template>Komponen parent menggunakan sintaks v-model standar:
<!-- ParentForm.vue -->
<script setup lang="ts">
import { ref } from 'vue'
import TextInput from './TextInput.vue'
const username = ref('')
</script>
<template>
<TextInput v-model="username" />
<p>Username: {{ username }}</p>
</template>defineModel mengembalikan ref yang tetap tersinkronisasi dengan nilai yang di-bind dari parent. Mutasi pada ref ini secara otomatis emit update event.
Siap menguasai wawancara Vue.js / Nuxt.js Anda?
Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.
Named Models dan Multiple v-model Bindings
Komponen terkadang membutuhkan multiple two-way bindings. Vue mendukung named v-model bindings, dan defineModel menanganinya dengan menerima argumen nama.
<!-- DateRangePicker.vue -->
<script setup lang="ts">
// Named models untuk multiple v-model bindings
const startDate = defineModel<Date>('start', { required: true })
const endDate = defineModel<Date>('end', { required: true })
// Validation computed
const isValidRange = computed(() => {
if (!startDate.value || !endDate.value) return true
return startDate.value <= endDate.value
})
</script>
<template>
<div class="date-range-picker">
<input
type="date"
:value="startDate?.toISOString().split('T')[0]"
@input="startDate = new Date(($event.target as HTMLInputElement).value)"
/>
<span>to</span>
<input
type="date"
:value="endDate?.toISOString().split('T')[0]"
@input="endDate = new Date(($event.target as HTMLInputElement).value)"
/>
<p v-if="!isValidRange" class="error">End date must be after start date</p>
</div>
</template>Parent mengikat kedua model:
<DateRangePicker v-model:start="filterStart" v-model:end="filterEnd" />Modifier v-model dengan defineModel
Modifier bawaan Vue seperti .trim, .number, dan .lazy bekerja secara otomatis dengan defineModel. Modifier kustom memerlukan penanganan eksplisit melalui elemen kedua dari tuple yang dikembalikan.
<!-- CurrencyInput.vue -->
<script setup lang="ts">
// Akses modifiers melalui nilai return kedua
const [model, modifiers] = defineModel<number>({
default: 0,
// Fungsi transform untuk modifier kustom
set(value) {
// Bulatkan ke 2 desimal jika modifier 'round' ada
if (modifiers.round) {
return Math.round(value * 100) / 100
}
return value
}
})
</script>
<template>
<div class="currency-input">
<span>$</span>
<input
type="number"
step="0.01"
:value="model"
@input="model = parseFloat(($event.target as HTMLInputElement).value) || 0"
/>
</div>
</template>Penggunaan dengan modifier kustom:
<CurrencyInput v-model.round="price" />Pola TypeScript untuk Script Setup
Type safety di komponen <script setup> bergantung pada generics yang diteruskan ke compiler macros. Polanya sedikit berbeda antara props, emits, dan models.
<!-- GenericList.vue -->
<script setup lang="ts" generic="T extends { id: string }">
import { computed } from 'vue'
// Generic component props
const props = defineProps<{
items: T[]
selected?: T
}>()
const emit = defineEmits<{
select: [item: T]
remove: [id: string]
}>()
// Slots juga bisa di-type
defineSlots<{
default: (props: { item: T; index: number }) => any
empty: () => any
}>()
const hasItems = computed(() => props.items.length > 0)
</script>
<template>
<ul v-if="hasItems">
<li
v-for="(item, index) in items"
:key="item.id"
:class="{ selected: selected?.id === item.id }"
@click="emit('select', item)"
>
<slot :item="item" :index="index">
{{ item.id }}
</slot>
</li>
</ul>
<div v-else>
<slot name="empty">No items</slot>
</div>
</template>Generic components, tersedia sejak Vue 3.3, memungkinkan komponen typed yang reusable tanpa mengorbankan type inference di kode yang mengkonsumsinya. Untuk pola lebih dalam, dokumentasi Vue tentang TypeScript mencakup edge cases.
Pertanyaan Interview Umum tentang Script Setup
Pewawancara yang menguji pengetahuan Vue sering fokus pada perbedaan antara script setup dan Options API, serta mekanisme reaktivitas.
Pertanyaan: Mengapa destructuring props merusak reaktivitas, dan bagaimana Vue 3.5 menyelesaikan ini?
Dalam JavaScript, destructuring membuat variabel baru yang menyimpan nilai pada saat destructure. Jika props.count berubah kemudian, const { count } = props yang di-destructure tetap menyimpan nilai lama. Compiler Vue 3.5 mentransformasi variabel yang di-destructure menjadi getters yang mengevaluasi ulang saat diakses, mempertahankan reaktivitas tanpa overhead runtime.
Pertanyaan: Apa yang terjadi di balik layar ketika defineModel digunakan?
Compiler mengembangkan defineModel menjadi deklarasi prop dan registrasi emit. Pada runtime, ini membuat ref yang getter-nya membaca nilai prop dan setter-nya memanggil emit('update:modelValue', newValue). Ref ini bukan salinan sejati dari prop tetapi proxy yang meneruskan reads dan writes dengan tepat.
Pertanyaan: Kapan komponen harus menggunakan script setup versus fungsi setup reguler?
Script setup lebih disukai untuk kebanyakan single-file components karena mengurangi boilerplate dan meningkatkan dukungan IDE. Fungsi setup() reguler diperlukan ketika komponen perlu mengekspos methods ke komponen parent melalui defineExpose dalam pola kompleks, atau ketika membangun renderless components yang mengembalikan render functions secara langsung. Untuk persiapan interview, eksplorasi lebih lanjut pola Vue composables.
Migrasi dari Options API ke Script Setup
Codebase yang ada sering berisi komponen Options API yang dapat diuntungkan dari migrasi. Konversi mengikuti pola yang dapat diprediksi:
<!-- Sebelum: Options API -->
<script lang="ts">
import { defineComponent, PropType } from 'vue'
export default defineComponent({
props: {
items: { type: Array as PropType<string[]>, required: true }
},
emits: ['select'],
data() {
return { searchTerm: '' }
},
computed: {
filteredItems() {
return this.items.filter(item =>
item.toLowerCase().includes(this.searchTerm.toLowerCase())
)
}
},
methods: {
selectItem(item: string) {
this.$emit('select', item)
}
}
})
</script><!-- Sesudah: Script Setup -->
<script setup lang="ts">
import { ref, computed } from 'vue'
const props = defineProps<{ items: string[] }>()
const emit = defineEmits<{ select: [item: string] }>()
const searchTerm = ref('')
const filteredItems = computed(() =>
props.items.filter(item =>
item.toLowerCase().includes(searchTerm.value.toLowerCase())
)
)
function selectItem(item: string) {
emit('select', item)
}
</script>Versi yang dimigrasi lebih pendek dan type inference bekerja tanpa cast PropType. Tim Vue menyediakan vue-codemod untuk migrasi otomatis, meskipun review manual tetap diperlukan untuk komponen kompleks.
Pertimbangan Performa di Script Setup
Komponen script setup dikompilasi ke kode yang lebih efisien karena compiler mengetahui bentuk pasti dari komponen pada saat build. Secara spesifik:
- Template bindings resolve pada compile time daripada runtime property lookup
- Unused imports di-tree-shake lebih efektif
- Tidak adanya wrapping function mengurangi closure overhead
Untuk aplikasi yang memprioritaskan bundle size, script setup dengan Vapor Mode (tersedia di Vue 3.6+) menghilangkan virtual DOM sepenuhnya untuk komponen yang opted-in, menghasilkan operasi DOM langsung sebagai gantinya.
Mulai berlatih!
Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.
Kesimpulan Utama untuk Vue 3 Script Setup dan defineModel
- Script setup mengurangi boilerplate komponen dengan secara otomatis mengekspos top-level bindings ke templates dan menangani registrasi komponen
defineModelmenggantikan pola manual prop + emit untukv-model, mengembalikan ref yang sync dengan nilai yang di-bind parent- Vue 3.5 memperkenalkan reactive props destructure, memungkinkan
const { prop } = defineProps()tanpa kehilangan reaktivitas - Named models mendukung multiple
v-modelbindings pada satu komponen menggunakandefineModel('name') - TypeScript generics bekerja dengan
defineProps,defineEmits,defineSlots, dan generic components melalui atributgeneric - Migrasi dari Options API mengikuti pola mekanis: data menjadi refs, computed tetap computed, methods menjadi functions, dan compiler menangani exports
Bisakah kamu menemukan bug di Vue.js / Nuxt.js?
Satu potongan kode nyata, satu bug tersembunyi, satu percobaan per hari. Tanpa akun untuk mencoba.

Ditulis oleh
Anthony Fillion-MailletPendiri SharpSkill
Developer fullstack selama lebih dari 10 tahun. Ia menjalankan SharpSkill dan bertanggung jawab atas semua yang diterbitkan di sini.
Diperbarui 18 September 2026
Tag
Bagikan
Artikel terkait

Vue 3 Reactivity Transform di 2026: $ref, $computed dan Pertanyaan Interview
Panduan mendalam tentang Reactivity Transform Vue 3 meliputi $ref, $computed, dan pertanyaan interview yang sering diajukan untuk developer Vue senior.

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.

Vue 3 Composition API: Panduan Lengkap Menguasai Reaktivitas
Kuasai Vue 3 Composition API melalui panduan praktis ini. Pelajari ref, reactive, computed, watch, dan composables untuk membangun aplikasi Vue yang berkinerja tinggi.