schrittweise migrieren statt alles neu zu schreiben
Die Migration von der Options API zur Composition API muss keine Komponente auf einmal komplett umbauen. Vue 3 erlaubt beide Stile im selben Projekt gleichzeitig, wodurch data, methods und computed Komponente für Komponente sauber in die Composition API übersetzt werden können, während der Rest der Anwendung unverändert weiterläuft.
Inhaltsverzeichnis
- 1. Warum schrittweise migrieren statt alles neu zu schreiben
- 2. Migrationskandidaten priorisieren
- 3. data() zu ref() und reactive() übersetzen
- 4. methods zu einfachen Funktionen
- 5. computed und watch in der Composition API
- 6. Lifecycle-Hooks übersetzen
- 7. Wiederkehrende Logik in Composables auslagern
- 8. Typische Fallstricke bei der Migration
- 9. Options API und Composition API im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum schrittweise migrieren statt alles neu zu schreiben
Die Migration von der Options API zur Composition API wird in vielen Teams als Alles-oder-nichts-Entscheidung missverstanden. Dabei ist genau das Gegenteil der Fall: Vue 3 unterstützt beide Stile im selben Projekt und sogar in derselben Datei parallel, was eine schrittweise Migration ohne Funktionsverlust ermöglicht. Eine Komponente, die noch mit data() und methods arbeitet, funktioniert neben einer Schwesterkomponente, die bereits vollständig auf script setup umgestellt ist, ohne dass eine der beiden angepasst werden muss.
Der praktische Vorteil dieser Herangehensweise: Ein Team kann die Migration von der Options API zur Composition API über Monate strecken, immer dann, wenn eine Komponente ohnehin angefasst wird, etwa für einen Bugfix oder ein neues Feature. Das verhindert das klassische Risiko eines großen Refactoring-Sprints, bei dem hunderte Komponenten gleichzeitig verändert werden und Regressionen kaum noch einer einzelnen Änderung zugeordnet werden können. Die folgenden Abschnitte zeigen konkret, wie data, methods, computed und Lifecycle-Hooks übersetzt werden.
2. Migrationskandidaten priorisieren
Nicht jede Komponente profitiert gleich stark von der Migration von der Options API zur Composition API. Der größte Gewinn entsteht bei Komponenten, die viel Logik zwischen mehreren Stellen teilen, etwa über Mixins, oder bei Komponenten mit komplexer, schwer nachvollziehbarer data()-Struktur. Kleine, reine Präsentationskomponenten ohne eigene Logik bringen dagegen kaum Vorteile durch eine Migration und können bewusst zurückgestellt werden, ohne dass das Projekt darunter leidet.
Ein praktikables Priorisierungskriterium: Komponenten mit Mixins zuerst migrieren, weil Mixins in der Composition API durch Composables ersetzt werden und dort die stärkste strukturelle Verbesserung erzielen. Danach folgen Komponenten mit vielen watch-Optionen, weil watch() in der Composition API expliziter und einfacher zu debuggen ist. Zuletzt kommen einfache CRUD-Formulare, deren Migration zwar sinnvoll, aber selten dringend ist.
# Find migration candidates: components using mixins
grep -rl "mixins:" src/components/ | wc -l
# Components with heavy watch usage - good early candidates
grep -rl "watch:" src/components/ | wc -l
# Simple components without data() or methods - low priority
grep -rL "data()\|methods:" src/components/*.vue
3. data() zu ref() und reactive() übersetzen
Der Einstieg in die Migration von der Options API zur Composition API beginnt fast immer bei data(). Jede Eigenschaft, die data() zurückgibt, wird in der Composition API zu einem eigenen ref()-Aufruf, oder bei zusammengehörigen Objekten zu einem einzigen reactive()-Objekt. Der Unterschied ist mehr als Syntax: ref() erzwingt den Zugriff über .value im Script-Teil, während reactive() direkte Property-Zugriffe erlaubt, aber keine Ersetzung des gesamten Objekts unterstützt, ohne die Reaktivität zu verlieren.
Die gängige Empfehlung bei der Migration: ref() für primitive Werte wie Strings, Zahlen und Booleans, reactive() nur für Objekte, die als Ganzes zusammengehören und selten komplett ersetzt werden. Wer stattdessen alles in ein großes reactive()-Objekt packt, weil das der alten data()-Struktur ähnelt, verliert einen Teil der Klarheit, die die Composition API eigentlich bringen soll.
// Options API: data() returning multiple properties
export default {
data() {
return {
searchTerm: '',
isLoading: false,
results: [],
filters: { category: 'all', inStock: true },
}
},
}
// Composition API: primitives as ref(), grouped object as reactive()
import { ref, reactive } from 'vue'
const searchTerm = ref('')
const isLoading = ref(false)
const results = ref([])
const filters = reactive({ category: 'all', inStock: true })
// Access in script: ref needs .value, reactive does not
searchTerm.value = 'vue migration'
filters.category = 'electronics'
4. methods zu einfachen Funktionen
Der Übergang von methods zu einfachen Funktionen ist der unkomplizierteste Teil der Migration von der Options API zur Composition API. Jede Methode wird zu einer normalen JavaScript-Funktion innerhalb von setup() oder script setup, ohne this-Bindung. Das entfernt eine ganze Fehlerklasse: verlorene this-Referenzen in Callbacks, Arrow-Function-Fallen und die Notwendigkeit, Methoden in Event-Handlern manuell mit .bind(this) zu versehen, entfallen vollständig.
Ein Detail, das bei der Migration oft übersehen wird: Methoden, die in der Options API auf data-Properties über this.property zugegriffen haben, greifen in der Composition API direkt auf die entsprechende Variable zu, ohne Präfix. Bei ref()-Werten muss innerhalb der Funktion weiterhin .value verwendet werden, im Template dagegen automatisch nicht, da Vue Refs im Template-Kontext automatisch entpackt.
// Options API: methods with this-binding
export default {
data() { return { count: 0 } },
methods: {
increment() {
this.count++
this.logChange(this.count)
},
logChange(value) {
console.log(`Count changed to ${value}`)
},
},
}
// Composition API: plain functions, no this needed
import { ref } from 'vue'
const count = ref(0)
function logChange(value) {
console.log(`Count changed to ${value}`)
}
function increment() {
count.value++
logChange(count.value) // direct call, no this.
}
5. computed und watch in der Composition API
computed-Eigenschaften übersetzen sich bei der Migration von der Options API zur Composition API fast eins zu eins: aus einer Funktion im computed-Objekt wird ein computed()-Aufruf, der einen Ref zurückgibt. Die inhaltliche Logik bleibt unverändert, nur die Syntax drumherum ändert sich. Bei watch ist der Unterschied größer: Die Composition API verlangt eine explizite Quelle als erstes Argument, entweder einen Ref, eine reaktive Property als Getter-Funktion oder ein Array mehrerer Quellen, statt des impliziten Property-Namens als String-Key im Options-Objekt.
Dieser explizitere Ansatz bei watch() hat einen praktischen Vorteil während der Migration: Es wird sofort sichtbar, welche genaue Quelle beobachtet wird, statt sich auf einen String-Key zu verlassen, der bei Refactorings leicht veraltet. Für tief verschachtelte Objekte muss zusätzlich { deep: true } übergeben werden, was in der Options API über handler und deep: true im gleichen Objekt gelöst wurde und strukturell sehr ähnlich bleibt.
// Options API: computed and watch
export default {
data() { return { firstName: 'Anna', lastName: 'Muster', cart: { items: [] } } },
computed: {
fullName() {
return `${this.firstName} ${this.lastName}`
},
},
watch: {
firstName(newVal, oldVal) {
console.log(`Name changed from ${oldVal} to ${newVal}`)
},
cart: { handler() { this.recalculateTotal() }, deep: true },
},
}
// Composition API: computed() and watch() with explicit source
import { ref, reactive, computed, watch } from 'vue'
const firstName = ref('Anna')
const lastName = ref('Muster')
const cart = reactive({ items: [] })
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
watch(firstName, (newVal, oldVal) => {
console.log(`Name changed from ${oldVal} to ${newVal}`)
})
watch(cart, () => recalculateTotal(), { deep: true })
6. Lifecycle-Hooks übersetzen
Lifecycle-Hooks folgen bei der Migration von der Options API zur Composition API einem konsistenten Namensschema: mounted wird zu onMounted(), updated zu onUpdated(), beforeUnmount zu onBeforeUnmount(). Jeder Hook wird als Funktion importiert und innerhalb von setup() mit einem Callback aufgerufen. Eine wichtige Ausnahme: created und beforeCreate haben kein direktes Äquivalent, weil deren Code in der Composition API einfach direkt im Top-Level von setup() steht, noch bevor irgendein Lifecycle-Hook registriert wird.
Ein Vorteil, der bei der Migration sofort sichtbar wird: Mehrere onMounted()-Aufrufe innerhalb derselben Komponente sind erlaubt und werden der Reihe nach ausgeführt. Das ist besonders nützlich, wenn Logik aus verschiedenen Composables importiert wird, die jeweils eigene Mounted-Hooks registrieren, ohne dass diese in einem einzigen großen mounted()-Handler zusammengeführt werden müssen wie in der Options API.
// Options API: lifecycle hooks as object keys
export default {
created() {
console.log('created - runs before setup exists in Options API')
},
mounted() {
this.fetchData()
},
beforeUnmount() {
this.cleanupListeners()
},
}
// Composition API: imported hook functions, created logic runs top-level
import { onMounted, onBeforeUnmount } from 'vue'
// Equivalent of created() / beforeCreate(): just runs here, top-level
console.log('runs immediately when setup executes')
onMounted(() => {
fetchData()
})
onBeforeUnmount(() => {
cleanupListeners()
})
7. Wiederkehrende Logik in Composables auslagern
Der eigentliche strategische Gewinn bei der Migration von der Options API zur Composition API liegt selten in einzelnen Komponenten, sondern in der Möglichkeit, wiederkehrende Logik in Composables auszulagern. Was in der Options API als Mixin gelöst wurde, mit den bekannten Problemen unklarer Namensherkunft und Property-Kollisionen, wird in der Composition API zu einer Funktion, die explizit importierte, benannte Werte zurückgibt. Ein Composable für Paginierung, Formularvalidierung oder API-Anfragen lässt sich in beliebig vielen Komponenten ohne Namenskonflikte wiederverwenden.
Bei der Migration lohnt es sich, bestehende Mixins zuerst in Composables zu übersetzen, bevor die eigentlichen Komponenten migriert werden. Dadurch profitieren auch Komponenten, die noch mit der Options API arbeiten, sofort von saubererer Logik, da Composables über setup() auch in Options-API-Komponenten nutzbar sind, sofern setup() als zusätzliche Option definiert wird.
8. Typische Fallstricke bei der Migration
Der häufigste Fehler bei der Migration von der Options API zur Composition API: Ein ref()-Wert wird destrukturiert, wodurch die Reaktivität verloren geht. const { count } = someObject löst die Verbindung zum ursprünglichen Ref, sobald someObject eine reaktive Struktur ist. Die korrekte Alternative ist toRefs(), das ein reaktives Objekt in einzelne Refs umwandelt, ohne die Reaktivität zu brechen.
Ein zweiter häufiger Fehler betrifft reactive()-Objekte, die komplett ersetzt werden: filters = { category: 'x' } überschreibt die Referenz und verliert damit die Reaktivität, weil Vue die alte Proxy-Referenz nicht mehr beobachtet. Der korrekte Weg ist, einzelne Properties zu setzen oder Object.assign(filters, { category: 'x' }) zu verwenden. Diese beiden Fallstricke betreffen fast jedes Migrationsprojekt und sollten früh im Team kommuniziert werden.
9. Options API und Composition API im Vergleich
Die folgende Tabelle fasst die wichtigsten Übersetzungsregeln zusammen, die bei der Migration von der Options API zur Composition API in praktisch jeder Komponente vorkommen.
| Options API | Composition API | Wichtiger Unterschied |
|---|---|---|
data() |
ref() / reactive() |
ref() braucht .value im Script |
methods |
Einfache Funktionen | Kein this, keine Bind-Probleme |
computed |
computed() |
Nahezu identische Logik |
watch: { prop() {} } |
watch(source, cb) |
Explizite Quelle statt String-Key |
mixins |
Composables | Explizite Rückgabewerte, keine Kollisionen |
Die Tabelle zeigt, dass fast jede Übersetzung syntaktisch überschaubar ist. Der eigentliche Aufwand bei der Migration von der Options API zur Composition API liegt nicht in der Syntax, sondern im sorgfältigen Umgang mit Reaktivität, insbesondere bei Destrukturierung und Objektersetzung.
Mironsoft
Vue-3-Refactoring ohne Big-Bang-Rewrite
Options API zu Composition API migrieren, ohne das Team auszubremsen?
Wir priorisieren Migrationskandidaten, extrahieren Mixins als Composables und begleiten die Umstellung von data, methods und watch Komponente für Komponente.
Priorisierung
Komponenten nach Migrationsnutzen sortieren, Mixins zuerst angehen
Composable-Extraktion
Mixins und Utility-Logik in wiederverwendbare Composables überführen
Reaktivitäts-Review
Destrukturierungs- und Objektersetzungs-Fallen vor dem Merge prüfen
10. Zusammenfassung
Die Migration von der Options API zur Composition API muss kein riskantes Großprojekt sein, weil Vue 3 beide Stile parallel unterstützt. Die Übersetzung folgt klaren Regeln: data() wird zu ref() oder reactive(), methods zu einfachen Funktionen ohne this, computed bleibt inhaltlich fast identisch, watch braucht eine explizite Quelle, und Lifecycle-Hooks folgen dem on-Präfix-Schema. Mixins werden durch Composables ersetzt und lösen dabei gleich zwei alte Probleme: unklare Namensherkunft und Property-Kollisionen.
Der zuverlässigste Weg ist die Priorisierung nach Nutzen: Komponenten mit Mixins und komplexer watch-Logik zuerst, einfache Präsentationskomponenten zuletzt oder gar nicht. Wer zusätzlich auf die beiden häufigsten Reaktivitäts-Fallen achtet, Destrukturierung von Refs und komplette Objektersetzung bei reactive(), vermeidet die meisten Bugs, die bei der Migration von der Options API zur Composition API typischerweise auftreten.
Options API zu Composition API migrieren — Das Wichtigste auf einen Blick
Beide Stile koexistieren
Vue 3 erlaubt Options API und Composition API im selben Projekt. Migration Komponente für Komponente ohne Funktionsverlust.
Mixins zuerst migrieren
Mixins werden zu Composables und bringen den größten strukturellen Gewinn. Priorität vor einfachen Präsentationskomponenten.
Reaktivität nicht brechen
Refs nicht destrukturieren ohne toRefs(). reactive()-Objekte nicht komplett ersetzen, sondern Properties einzeln setzen.
Lifecycle-Hooks per Präfix
mounted wird onMounted(), beforeUnmount wird onBeforeUnmount(). Mehrere Aufrufe pro Hook sind erlaubt.