Vue 3 Script Setup ve defineModel 2026: Modern Sözdizimi ve Mülakat Soruları

Vue 3 script setup sözdizimi ve defineModel makrosu hakkında kapsamlı rehber. Reactive props destructure, TypeScript entegrasyonu ve mülakat soruları.

Vue 3 Script Setup ve defineModel rehberi

Vue 3 script setup sözdizimi, bileşen yazım şeklini dönüştürerek gereksiz şablon kodunu ortadan kaldırır ve TypeScript entegrasyonunu önemli ölçüde iyileştirir. Vue 3.3'te deneysel olarak tanıtılan ve Vue 3.4'te stabilize edilen defineModel ile birleştirildiğinde, iki yönlü veri bağlama içeren bileşenler oluşturmak Options API eşdeğerine göre çok daha az kod gerektirir.

Vue 3.5 Reactive Props Destructure

Vue 3.5'ten itibaren props, reaktiviteyi koruyarak doğrudan defineProps'tan destructure edilebilir. Bu, çoğu durumda toRefs kullanma ihtiyacını ortadan kaldırır.

Vue 3.5'te Script Setup Temelleri

<script setup> sözdizimi, Single File Components içinde Composition API için derleme zamanı sözdizimsel şekeridir. En üst düzeyde bildirilen değişkenler otomatik olarak şablona sunulur ve importlar kayıt olmadan doğrudan kullanılabilir.

vue
<!-- UserCard.vue -->
<script setup lang="ts">
import { ref, computed } from 'vue'
import type { User } from '@/types'

// TypeScript interface ile Props
const props = defineProps<{
  user: User
  showEmail?: boolean
}>()

// Reaktif durum
const isExpanded = ref(false)

// Computed özellik
const displayName = computed(() => {
  return `${props.user.firstName} ${props.user.lastName}`
})

// Typed payload ile event emitter
const emit = defineEmits<{
  select: [userId: string]
  delete: [userId: string]
}>()

// Metodlar düz fonksiyonlardır
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 ? 'Daralt' : 'Genişlet' }}
    </button>
  </div>
</template>

Derleyici, export ifadelerini ve bileşen kaydını otomatik olarak işler. Bu, setup() fonksiyonundan açıkça değer döndürmeye kıyasla bilişsel yükü azaltır.

Reactive Props Destructure (Vue 3.5+)

Vue 3.5'ten önce, props'u destructure etmek reaktiviteyi bozuyordu çünkü JavaScript destructure işlemi o anki değerin bir anlık görüntüsünü oluşturur. Vue 3.5, destructure edilmiş props için reaktiviteyi koruyan derleyici düzeyinde izleme sunar.

vue
<!-- SearchInput.vue -->
<script setup lang="ts">
import { watch } from 'vue'

// Varsayılan değerlerle destructure - 3.5+'de reaktif kalır
const { query = '', placeholder = 'Ara...' } = defineProps<{
  query?: string
  placeholder?: string
}>()

// Watch, destructure edilmiş props üzerinde çalışır
watch(
  () => query,
  (newQuery) => {
    console.log('Sorgu değişti:', newQuery)
  }
)
</script>

<template>
  <input
    :value="query"
    :placeholder="placeholder"
    @input="$emit('update:query', ($event.target as HTMLInputElement).value)"
  />
</template>

Bu sözdizimi Vue 3.5 veya üstünü gerektirir. Vue 3.5 sürüm duyurusu reactive props destructure davranışının tam listesini belgelemektedir.

İki Yönlü Bağlama için defineModel

defineModel'den önce, özel bir bileşende v-model uygulamak bir prop bildirme ve manuel olarak güncelleme olayı emit etme gerektiriyordu. Bu pattern sayısız Vue kod tabanında görülür:

vue
<!-- Eski yol - ayrıntılı ama açık -->
<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>

defineModel ile aynı bileşen önemli ölçüde kısalır:

vue
<!-- TextInput.vue -->
<script setup lang="ts">
// defineModel, v-model ile senkronize olan bir ref oluşturur
const model = defineModel<string>({ default: '' })
</script>

<template>
  <input
    v-model="model"
    type="text"
    class="text-input"
  />
</template>

Üst bileşen standart v-model sözdizimini kullanır:

vue
<!-- 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>Kullanıcı adı: {{ username }}</p>
</template>

defineModel, üst bileşenin bağlı değeriyle senkronize kalan bir ref döndürür. Bu ref'e yapılan mutasyonlar otomatik olarak güncelleme olayını emit eder.

Vue.js / Nuxt.js mülakatlarında başarılı olmaya hazır mısın?

İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.

Adlandırılmış Modeller ve Çoklu v-model Bağlamaları

Bileşenler bazen birden fazla iki yönlü bağlamaya ihtiyaç duyar. Vue adlandırılmış v-model bağlamalarını destekler ve defineModel bunları bir ad argümanı kabul ederek işler.

vue
<!-- DateRangePicker.vue -->
<script setup lang="ts">
// Çoklu v-model bağlamaları için adlandırılmış modeller
const startDate = defineModel<Date>('start', { required: true })
const endDate = defineModel<Date>('end', { required: true })

// Doğrulama 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>-</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">Bitiş tarihi başlangıç tarihinden sonra olmalıdır</p>
  </div>
</template>

Üst bileşen her iki modeli de bağlar:

vue
<DateRangePicker v-model:start="filterStart" v-model:end="filterEnd" />

defineModel ile v-model Modifier'ları

Vue'nun .trim, .number ve .lazy gibi yerleşik modifier'ları defineModel ile otomatik olarak çalışır. Özel modifier'lar, döndürülen tuple'ın ikinci elemanı aracılığıyla açık işleme gerektirir.

vue
<!-- CurrencyInput.vue -->
<script setup lang="ts">
// Modifier'lara ikinci dönüş değeri aracılığıyla erişim
const [model, modifiers] = defineModel<number>({
  default: 0,
  // Özel modifier'lar için dönüşüm fonksiyonları
  set(value) {
    // 'round' modifier'ı mevcutsa 2 ondalık basamağa yuvarla
    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>

Özel modifier ile kullanım:

vue
<CurrencyInput v-model.round="price" />

Script Setup için TypeScript Kalıpları

<script setup> bileşenlerinde tip güvenliği, derleyici makrolarına geçirilen generics'e dayanır. Kalıp, props, emits ve modeller arasında biraz farklılık gösterir.

vue
<!-- GenericList.vue -->
<script setup lang="ts" generic="T extends { id: string }">
import { computed } from 'vue'

// Generic bileşen props'ları
const props = defineProps<{
  items: T[]
  selected?: T
}>()

const emit = defineEmits<{
  select: [item: T]
  remove: [id: string]
}>()

// Slot'lar da tiplenebilir
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">Öğe yok</slot>
  </div>
</template>

Vue 3.3'ten beri mevcut olan generic bileşenler, tüketen koddaki tip çıkarımını feda etmeden yeniden kullanılabilir tiplenmiş bileşenler oluşturmayı sağlar. Daha derin kalıplar için Vue TypeScript dokümantasyonu edge case'leri kapsar.

Script Setup Hakkında Yaygın Mülakat Soruları

Vue bilgisini test eden mülakatçılar genellikle script setup ve Options API arasındaki farklara ve reaktivite mekanizmasına odaklanır.

Soru: Props'u destructure etmek neden reaktiviteyi bozar ve Vue 3.5 bunu nasıl çözer?

JavaScript'te destructure, o andaki değerleri tutan yeni değişkenler oluşturur. props.count daha sonra değişirse, destructure edilmiş const { count } = props hala eski değeri tutar. Vue 3.5'in derleyicisi, destructure edilmiş değişkenleri erişildiğinde yeniden değerlendiren getter'lara dönüştürür ve runtime ek yükü olmadan reaktiviteyi korur.

Soru: defineModel kullanıldığında arka planda ne olur?

Derleyici defineModel'i bir prop bildirimi ve bir emit kaydına genişletir. Runtime'da, getter'ı prop değerini okuyan ve setter'ı emit('update:modelValue', newValue) çağıran bir ref oluşturur. Bu ref, prop'un gerçek bir kopyası değil, okuma ve yazmaları uygun şekilde yönlendiren bir proxy'dir.

Soru: Bir bileşen ne zaman script setup yerine normal setup fonksiyonu kullanmalıdır?

Script setup, şablon kodunu azalttığı ve IDE desteğini iyileştirdiği için çoğu single-file bileşen için tercih edilir. Normal setup() fonksiyonu, bileşenin defineExpose aracılığıyla karmaşık kalıplarda üst bileşenlere metod sunması gerektiğinde veya doğrudan render fonksiyonları döndüren renderless bileşenler oluştururken gereklidir. Mülakat hazırlığı için daha fazla Vue composables kalıpları keşfedin.

Options API'den Script Setup'a Geçiş

Mevcut kod tabanları genellikle geçişten faydalanabilecek Options API bileşenleri içerir. Dönüşüm öngörülebilir bir kalıp izler:

vue
<!-- Önce: 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>
vue
<!-- Sonra: 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>

Geçiş yapılan sürüm daha kısadır ve tip çıkarımı PropType cast'leri olmadan çalışır. Vue ekibi otomatik geçişler için vue-codemod sağlar, ancak karmaşık bileşenler için manuel inceleme gerekli kalır.

Script Setup'ta Performans Değerlendirmeleri

Script setup bileşenleri daha verimli koda derlenir çünkü derleyici, derleme zamanında bileşenin tam şeklini bilir. Özellikle:

  • Şablon bağlamaları runtime property lookup yerine derleme zamanında çözülür
  • Kullanılmayan import'lar daha etkili bir şekilde tree-shake edilir
  • Sarmalayıcı fonksiyon eksikliği closure ek yükünü azaltır

Bundle boyutunu önceliklendiren uygulamalar için, script setup ile Vapor Mode (Vue 3.6+'da mevcut) opt-in bileşenler için sanal DOM'u tamamen ortadan kaldırır ve bunun yerine doğrudan DOM işlemleri üretir.

Pratik yapmaya başla!

Mülakat simülatörleri ve teknik testlerle bilgini test et.

Vue 3 Script Setup ve defineModel için Temel Çıkarımlar

  • Script setup, üst düzey bağlamaları otomatik olarak şablonlara sunarak ve bileşen kaydını işleyerek bileşen şablon kodunu azaltır
  • defineModel, v-model için manuel prop + emit kalıbını değiştirir ve üst bileşenin bağlı değeriyle senkronize olan bir ref döndürür
  • Vue 3.5, reaktivite kaybı olmadan const { prop } = defineProps() yapılmasına izin veren reactive props destructure'ı sunar
  • Adlandırılmış modeller, defineModel('name') kullanarak tek bir bileşende birden fazla v-model bağlamasını destekler
  • TypeScript generics, defineProps, defineEmits, defineSlots ve generic attribute'ü aracılığıyla generic bileşenlerle çalışır
  • Options API'den geçiş mekanik bir kalıp izler: data ref'lere, computed computed kalır, metodlar fonksiyonlara dönüşür ve derleyici export'ları işler
Günün meydan okuması

Vue.js / Nuxt.js kodundaki hatayı bulabilir misin?

Gerçek bir kod parçası, gizli bir hata, günde bir deneme. Denemek için hesap gerekmez.

Anthony Fillion-Maillet

Yazan:

Anthony Fillion-Maillet

SharpSkill kurucusu

10 yılı aşkın süredir fullstack geliştirici. SharpSkill’i yönetiyor ve burada yayımlanan her şeyden sorumlu.

18 Eylül 2026 tarihinde güncellendi

Etiketler

#vue
#vue3
#script-setup
#definemodel
#typescript
#composition-api

Paylaş

İlgili makaleler