Vue 3 Script Setup und defineModel 2026: Moderne Syntax und Interview-Fragen
Umfassender Leitfaden zu Vue 3 script setup und defineModel für bidirektionale Datenbindung. Behandelt reaktive Props-Destrukturierung, TypeScript-Integration und typische Interview-Fragen.

Die Vue 3 Script Setup Syntax revolutioniert die Komponentenentwicklung durch die Eliminierung von Boilerplate-Code und verbesserte TypeScript-Integration. In Kombination mit defineModel, das in Vue 3.3 experimentell eingeführt und in Vue 3.4 stabilisiert wurde, erfordert die Erstellung von Komponenten mit bidirektionaler Datenbindung deutlich weniger Code als das Options API-Äquivalent.
Ab Vue 3.5 können Props direkt aus defineProps destrukturiert werden, während die Reaktivität erhalten bleibt. Dies macht toRefs in den meisten Fällen überflüssig.
Grundlagen von Script Setup in Vue 3.5
Die <script setup> Syntax ist ein Compile-Time-Syntaxzucker für die Composition API innerhalb von Single File Components. Variablen, die auf oberster Ebene deklariert werden, sind automatisch im Template verfügbar, und Importe können direkt ohne Registrierung verwendet werden.
<!-- UserCard.vue -->
<script setup lang="ts">
import { ref, computed } from 'vue'
import type { User } from '@/types'
// Props mit TypeScript-Interface
const props = defineProps<{
user: User
showEmail?: boolean
}>()
// Reaktiver State
const isExpanded = ref(false)
// Computed Property
const displayName = computed(() => {
return `${props.user.firstName} ${props.user.lastName}`
})
// Event Emitter mit typisiertem Payload
const emit = defineEmits<{
select: [userId: string]
delete: [userId: string]
}>()
// Methoden sind einfache Funktionen
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 ? 'Einklappen' : 'Aufklappen' }}
</button>
</div>
</template>Der Compiler übernimmt Export-Statements und Komponentenregistrierung automatisch. Dies reduziert die kognitive Belastung im Vergleich zum expliziten Zurückgeben von Werten aus einer setup() Funktion.
Reaktive Props-Destrukturierung (Vue 3.5+)
Vor Vue 3.5 brach die Destrukturierung von Props die Reaktivität, da JavaScript-Destrukturierung einen Snapshot des Wertes zu diesem Zeitpunkt erstellt. Vue 3.5 führt Compiler-Level-Tracking ein, das die Reaktivität für destrukturierte Props erhält.
<!-- SearchInput.vue -->
<script setup lang="ts">
import { watch } from 'vue'
// Destrukturierung mit Defaults - bleibt reaktiv in 3.5+
const { query = '', placeholder = 'Suchen...' } = defineProps<{
query?: string
placeholder?: string
}>()
// Watch funktioniert mit destrukturierten Props
watch(
() => query,
(newQuery) => {
console.log('Query geändert:', newQuery)
}
)
</script>
<template>
<input
:value="query"
:placeholder="placeholder"
@input="$emit('update:query', ($event.target as HTMLInputElement).value)"
/>
</template>Diese Syntax erfordert Vue 3.5 oder höher. Die Vue 3.5 Release-Ankündigung dokumentiert das vollständige Verhalten der reaktiven Props-Destrukturierung.
defineModel für Bidirektionale Bindung
Vor defineModel erforderte die Implementierung von v-model für eine benutzerdefinierte Komponente das Deklarieren eines Props und das manuelle Emittieren eines Update-Events. Dieses Muster findet sich in zahlreichen Vue-Codebasen:
<!-- Der alte Weg - ausführlich aber explizit -->
<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>Mit defineModel wird dieselbe Komponente erheblich kürzer:
<!-- TextInput.vue -->
<script setup lang="ts">
// defineModel erstellt ein Ref, das mit v-model synchronisiert
const model = defineModel<string>({ default: '' })
</script>
<template>
<input
v-model="model"
type="text"
class="text-input"
/>
</template>Die Elternkomponente verwendet die Standard v-model Syntax:
<!-- 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>Benutzername: {{ username }}</p>
</template>defineModel gibt ein Ref zurück, das mit dem gebundenen Wert der Elternkomponente synchronisiert bleibt. Mutationen an diesem Ref lösen automatisch das Update-Event aus.
Bereit für deine Vue.js / Nuxt.js-Interviews?
Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.
Benannte Models und Mehrfache v-model Bindungen
Komponenten benötigen manchmal mehrere bidirektionale Bindungen. Vue unterstützt benannte v-model Bindungen, und defineModel behandelt diese durch Akzeptieren eines Namensarguments.
<!-- DateRangePicker.vue -->
<script setup lang="ts">
import { computed } from 'vue'
// Benannte Models für mehrfache v-model Bindungen
const startDate = defineModel<Date>('start', { required: true })
const endDate = defineModel<Date>('end', { required: true })
// Validierungs-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>bis</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">Enddatum muss nach dem Startdatum liegen</p>
</div>
</template>Die Elternkomponente bindet beide Models:
<DateRangePicker v-model:start="filterStart" v-model:end="filterEnd" />v-model Modifiers mit defineModel
Vues eingebaute Modifiers wie .trim, .number und .lazy funktionieren automatisch mit defineModel. Benutzerdefinierte Modifiers erfordern explizite Behandlung durch das zweite Element des zurückgegebenen Tupels.
<!-- CurrencyInput.vue -->
<script setup lang="ts">
// Zugriff auf Modifiers über den zweiten Rückgabewert
const [model, modifiers] = defineModel<number>({
default: 0,
// Transformationsfunktionen für benutzerdefinierte Modifiers
set(value) {
// Auf 2 Dezimalstellen runden, wenn 'round' Modifier vorhanden
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>Verwendung mit dem benutzerdefinierten Modifier:
<CurrencyInput v-model.round="price" />TypeScript-Muster für Script Setup
Typsicherheit in <script setup> Komponenten basiert auf Generics, die an Compiler-Makros übergeben werden. Das Muster variiert leicht zwischen Props, Emits und Models.
<!-- GenericList.vue -->
<script setup lang="ts" generic="T extends { id: string }">
import { computed } from 'vue'
// Generische Komponenten-Props
const props = defineProps<{
items: T[]
selected?: T
}>()
const emit = defineEmits<{
select: [item: T]
remove: [id: string]
}>()
// Slots können ebenfalls typisiert werden
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">Keine Einträge</slot>
</div>
</template>Generische Komponenten, verfügbar seit Vue 3.3, ermöglichen wiederverwendbare typisierte Komponenten ohne Einbußen bei der Typinferenz im konsumierenden Code.
Häufige Interview-Fragen zu Script Setup
Interviewer, die Vue-Kenntnisse testen, fokussieren sich oft auf die Unterschiede zwischen Script Setup und dem Options API sowie auf die Mechanik der Reaktivität.
Frage: Warum bricht die Destrukturierung von Props die Reaktivität und wie löst Vue 3.5 dies?
In JavaScript erstellt Destrukturierung neue Variablen, die die Werte zum Zeitpunkt der Destrukturierung halten. Wenn sich props.count später ändert, hält ein destrukturiertes const { count } = props immer noch den alten Wert. Der Compiler von Vue 3.5 transformiert die destrukturierten Variablen in Getter, die bei Zugriff neu evaluieren und somit die Reaktivität ohne Laufzeit-Overhead erhalten.
Frage: Was passiert unter der Haube, wenn defineModel verwendet wird?
Der Compiler expandiert defineModel zu einer Prop-Deklaration und einer Emit-Registrierung. Zur Laufzeit erstellt es ein Ref, dessen Getter den Prop-Wert liest und dessen Setter emit('update:modelValue', newValue) aufruft. Dieses Ref ist keine echte Kopie des Props, sondern ein Proxy, der Lese- und Schreiboperationen entsprechend weiterleitet.
Frage: Wann sollte eine Komponente Script Setup versus eine reguläre setup-Funktion verwenden?
Script Setup wird für die meisten Single File Components bevorzugt, da es Boilerplate reduziert und die IDE-Unterstützung verbessert. Eine reguläre setup() Funktion ist notwendig, wenn die Komponente Methoden über defineExpose in komplexen Mustern an Elternkomponenten exponieren muss, oder beim Erstellen von renderless Components, die Render-Funktionen direkt zurückgeben.
Migration von Options API zu Script Setup
Bestehende Codebasen enthalten oft Options API Komponenten, die von einer Migration profitieren. Die Konvertierung folgt einem vorhersagbaren Muster:
<!-- Vorher: 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><!-- Nachher: 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>Die migrierte Version ist kürzer und die Typinferenz funktioniert ohne PropType Casts. Das Vue-Team stellt vue-codemod für automatisierte Migrationen bereit, obwohl eine manuelle Überprüfung für komplexe Komponenten weiterhin notwendig ist.
Performance-Überlegungen bei Script Setup
Script Setup Komponenten kompilieren zu effizienterem Code, da der Compiler die exakte Form der Komponente zur Build-Zeit kennt. Konkret bedeutet das:
- Template-Bindungen werden zur Compile-Zeit aufgelöst statt zur Laufzeit durch Property-Lookup
- Ungenutzte Importe werden effektiver durch Tree-Shaking entfernt
- Das Fehlen einer umschließenden Funktion reduziert den Closure-Overhead
Für Anwendungen, die Bundle-Größe priorisieren, eliminiert Script Setup mit Vapor Mode (verfügbar ab Vue 3.6+) das virtuelle DOM vollständig für opt-in Komponenten und generiert stattdessen direkte DOM-Operationen.
Fang an zu üben!
Teste dein Wissen mit unseren Interview-Simulatoren und technischen Tests.
Kernpunkte zu Vue 3 Script Setup und defineModel
- Script Setup reduziert Komponenten-Boilerplate durch automatisches Exponieren von Top-Level-Bindungen an Templates und automatische Komponentenregistrierung
defineModelersetzt das manuelle Prop + Emit Pattern fürv-modelund gibt ein Ref zurück, das mit dem gebundenen Wert der Elternkomponente synchronisiert- Vue 3.5 führt reaktive Props-Destrukturierung ein, die
const { prop } = defineProps()ohne Verlust der Reaktivität ermöglicht - Benannte Models unterstützen mehrfache
v-modelBindungen auf einer einzelnen Komponente durchdefineModel('name') - TypeScript-Integration verbessert sich mit generischen Komponenten, typisierten Emits und typisierter Slot-Unterstützung durch Compiler-Makros
Findest du den Bug in Vue.js / Nuxt.js?
Ein echter Codeausschnitt, ein versteckter Bug, ein Versuch pro Tag. Zum Ausprobieren ohne Konto.

Geschrieben von
Anthony Fillion-MailletGründer von SharpSkill
Seit über 10 Jahren Fullstack-Entwickler. Er leitet SharpSkill und verantwortet alles, was hier erscheint.
Aktualisiert am 18. September 2026
Tags
Teilen
Verwandte Artikel

Fortgeschrittene Vue 3 Composables: Wiederverwendbare Muster und Interviewfragen 2026
Umfassender Leitfaden zu fortgeschrittenen Vue 3 Composables mit wiederverwendbaren Mustern, asynchroner Fehlerbehandlung, Dependency Injection, Formularvalidierung und aktuellen Interviewfragen fuer 2026.

Nuxt 4 in 2026: Neue Verzeichnisstruktur und Migration von Nuxt 3
Nuxt 4 bringt eine neue app/-Verzeichnisstruktur, verbessertes Data-Fetching, Shallow Reactivity und Unhead v2. Kompletter Migrationsleitfaden von Nuxt 3 mit Codemod-Automatisierung.

Vue 3 Reactivity Transform 2026: $ref, $computed und Interview-Fragen
Umfassende Analyse des Vue 3 Reactivity Transform mit $ref, $computed und $$ Makros. Praktische Codebeispiele, Migration von ref() zu $ref, und häufige Vue-Interview-Fragen für Senior-Entwickler.