Kleine, ruhige Vue-Komponenten bauen
State Explosion ist das häufigste Wartbarkeitsproblem in wachsenden Vue-Projekten: Komponenten akkumulieren reaktive Variablen, Watcher und lokalen State, bis sie nicht mehr verstehbar sind. Der Ausweg liegt darin, State zu minimieren, Derived State aus Props abzuleiten und Reaktivität nur dort einzusetzen, wo sie wirklich gebraucht wird.
Inhaltsverzeichnis
- 1. Was State Explosion in Vue bedeutet und warum sie entsteht
- 2. Lokalen State minimieren: Was wirklich reaktiv sein muss
- 3. Derived State: computed statt reactive Datenkopien
- 4. Watcher sind meistens das falsche Werkzeug
- 5. Single Source of Truth und Daten-Ownership
- 6. Pinia gezielt einsetzen: Nicht alles in den Store
- 7. Props-down, Events-up: Datenfluss diszipliniert halten
- 8. Komponentenzerlegung als State-Komplexitäts-Reduzierer
- 9. State-Muster im Vergleich: reaktiv vs. abgeleitet vs. extern
- 10. Zusammenfassung
- 11. FAQ
1. Was State Explosion in Vue bedeutet und warum sie entsteht
State Explosion in Vue beschreibt den Zustand, in dem eine Komponente so viele reaktive Variablen, Watcher und lokale State-Fragmente akkumuliert, dass das Lesen und Verstehen des Codes unverhältnismäßig schwer wird. Die typische Symptomatik: Eine Komponente mit dreißig ref()- und reactive()-Deklarationen, zehn watch-Aufrufen, die auf gegenseitige Abhängigkeiten reagieren, und mehreren computed Properties, die eigentlich synchronisierte Kopien anderer States sind. Das Ändern eines Wertes löst eine Kaskade von Watchers aus, die sich gegenseitig triggern — und plötzlich werden UI-Updates ausgelöst, die niemand erwartet hat.
Die Ursache von State Explosion in Vue liegt meistens nicht in schlechten Absichten, sondern in natürlichen Erweiterungen über die Zeit. Feature A braucht eine neue reactive Variable. Feature B braucht einen Watcher auf diese Variable. Feature C synchronisiert einen weiteren State mit beiden. Nach sechs Monaten ist die Komponente nicht mehr verstehbar, ohne jeden einzelnen State-Pfad nachzuverfolgen. Der Ausweg liegt in frühen Architekturentscheidungen: Minimaler State, maximaler Derived State, klare Ownership und disziplinierter Einsatz von Reaktivität nur dort, wo sie echten Wert liefert.
2. Lokalen State minimieren: Was wirklich reaktiv sein muss
Die wichtigste Frage bei jeder Vue-Komponente ist: Welcher State muss wirklich reaktiv und lokal sein? Die Antwort ist meistens: deutlich weniger als implementiert. UI-State wie isOpen, isLoading oder activeTab sind legitimer lokaler reaktiver State — sie beschreiben den Zustand der Komponente, nicht die Domänendaten. Domänendaten hingegen gehören nicht als Kopie in den lokalen State: Wenn Props Daten enthalten, sollte nie eine reaktive Kopie davon angelegt werden, die dann mit Watchers synchronisiert wird. Das ist eine klassische Form der State Explosion in Vue.
Reine UI-State-Variablen, die keine Auswirkungen außerhalb der Komponente haben und nicht von außen gesetzt werden, sind der Kernbereich lokaler Reaktivität. Alles andere sollte entweder via Props hereingegeben werden und dann als Derived State per computed abgeleitet werden, oder direkt aus einem Pinia-Store stammen. Eine Faustregel: Wenn eine reactive Variable nie direkt im Template verwendet wird und nur als Zwischenschritt für einen computed-Wert existiert, kann sie meistens durch eine direktere computed-Berechnung aus dem ursprünglichen Datenstrom ersetzt werden.
// BAD — State Explosion: reactive copy of prop with watcher to sync
const props = defineProps({ items: Array, selectedId: String })
// WRONG: local reactive copy creates divergence and watcher need
const localItems = ref([...props.items]) // Unnecessary copy
const selectedItem = ref(null)
// WRONG: watcher to sync — this is State Explosion in action
watch(() => props.selectedId, (id) => {
selectedItem.value = localItems.value.find(i => i.id === id)
})
watch(() => props.items, (newItems) => {
localItems.value = [...newItems] // Out-of-sync risk
})
// GOOD — Derived State: compute everything from props directly
// No local copy, no watcher, no sync risk
const selectedItem = computed(() =>
props.items.find(i => i.id === props.selectedId) ?? null
)
// Only real local UI state — belongs in the component
const isDropdownOpen = ref(false)
const searchQuery = ref('')
// Derived from local UI state + prop data — no extra state needed
const filteredItems = computed(() =>
props.items.filter(i =>
i.label.toLowerCase().includes(searchQuery.value.toLowerCase())
)
)
3. Derived State: computed statt reactive Datenkopien
Derived State in Vue ist State, der vollständig aus anderen State-Quellen berechnet wird und daher kein eigenes reaktives Datum benötigt. Das zentrale Werkzeug ist computed(): Ein computed-Wert wird automatisch aktualisiert, wenn seine Abhängigkeiten sich ändern. Er ist gecacht — solange keine Abhängigkeit sich ändert, wird die Berechnung nicht erneut ausgeführt. Und er ist readonly — das verhindert, dass zwei verschiedene Stellen im Code versuchen, denselben Wert zu schreiben, was der häufigste Weg ist, wie State-Inkonsistenzen entstehen.
Der Unterschied zwischen Derived State als computed und als separatem ref mit Watcher ist fundamental: Ein computed-Wert ist synchron, gecacht und immer konsistent mit seinen Quellen. Ein ref mit Watcher ist asynchron, kann einen veralteten Wert haben, bevor der Watcher ausgeführt wird, und erzeugt eine explizite Abhängigkeit, die beim Lesen des Codes verstanden werden muss. Jedes Mal, wenn ein Watcher State kopiert oder transformiert, ist das ein Zeichen, dass ein computed-Wert die richtige Lösung wäre. Die Faustregel: Wenn die Antwort auf "Wie wird dieser State berechnet?" klar und deterministisch ist, gehört es in ein computed.
4. Watcher sind meistens das falsche Werkzeug
Watcher in Vue sind das Werkzeug für Nebenwirkungen auf State-Änderungen: API-Aufrufe, Logging, externe Bibliotheksinteraktionen. Watcher sind nicht das richtige Werkzeug für State-Derivation, State-Synchronisation oder State-Transformation. Das ist der häufigste Fehlereinsatz von Watchers in Vue-Anwendungen — und direkte Quelle von State Explosion. Ein Watcher, der beim Ändern von Prop A einen computed-ähnlichen lokalen State B neu berechnet, sollte durch ein computed ersetzt werden. Ein Watcher, der beim Ändern von State A den State B setzt und gleichzeitig beim Ändern von State B den State A setzt, erzeugt zirkuläre Abhängigkeiten — ein Warnsignal für fundamentale State-Design-Probleme.
Die legitimen Anwendungsfälle für Watcher sind eng begrenzt: Datenfetching als Reaktion auf Route-Parameter-Änderungen, Interaktion mit nicht-reaktiven externen Bibliotheken (Charts, Maps), Persistierung von State in localStorage oder einer API, und das Auslösen von Animationen oder Fokus-Events. Alles andere — insbesondere State-zu-State-Synchronisation — sollte durch restructuring des State-Designs gelöst werden, nicht durch Watcher. Ein Projekt, in dem Watcher zahlreicher sind als computed Properties, hat mit hoher Wahrscheinlichkeit ein State-Design-Problem.
5. Single Source of Truth und Daten-Ownership
Das Prinzip der Single Source of Truth in Vue bedeutet: Jedes Datum hat exakt eine kanonische Quelle in der Anwendung, und alle anderen Darstellungen dieses Datums sind Derivationen davon. Wenn Produktdaten im Pinia-Store leben, gibt es keine lokale reaktive Kopie in der Komponente und keine zweite Version im localStorage-Cache ohne Synchronisationslogik. Das klingt trivial, ist aber in der Praxis die häufigste Quelle von State-Inkonsistenzen: Ein Nutzer ändert seinen Namen, der Store wird aktualisiert, aber eine Komponente, die eine lokale Kopie hält, zeigt noch den alten Namen.
Daten-Ownership in Vue bestimmt, welche Schicht des Datenflusses für ein bestimmtes Datum verantwortlich ist. UI-State wie Modal-Sichtbarkeit und Tab-Auswahl kann lokal in der Komponente gelebt werden — kein anderer Teil der Anwendung braucht darauf zuzugreifen. Anwendungszustand wie authentifizierter Nutzer, aktiver Warenkorb und globale Benachrichtigungen gehört in Pinia. Seiten-spezifischer State, der nur relevant ist, solange eine bestimmte Route aktiv ist, kann in einer Route-spezifischen Composable leben. Die Entscheidung, wo State lebt, ist die wichtigste Architekturentscheidung, um State Explosion in Vue zu verhindern.
// stores/cart.js — Single source of truth for cart state
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
export const useCartStore = defineStore('cart', () => {
const items = ref([])
// Derived state — no separate reactive variables needed
const itemCount = computed(() => items.value.reduce((n, i) => n + i.qty, 0))
const subtotal = computed(() =>
items.value.reduce((sum, i) => sum + i.price * i.qty, 0)
)
const isEmpty = computed(() => items.value.length === 0)
const addItem = (product, qty = 1) => {
const existing = items.value.find(i => i.id === product.id)
if (existing) { existing.qty += qty; return }
items.value.push({ ...product, qty })
}
const removeItem = (id) => {
items.value = items.value.filter(i => i.id !== id)
}
const updateQty = (id, qty) => {
const item = items.value.find(i => i.id === id)
if (item) item.qty = Math.max(1, qty)
}
return { items, itemCount, subtotal, isEmpty, addItem, removeItem, updateQty }
})
// CartButton.vue — No local state needed: everything comes from store
// const cart = useCartStore()
// cart.itemCount, cart.addItem, cart.isEmpty — all reactive, single source
6. Pinia gezielt einsetzen: Nicht alles in den Store
Die Gegenbewegung zu State Explosion in Vue-Komponenten ist nicht das Auslagern von allem State in Pinia. Auch ein Pinia-Store kann explodieren, wenn er die gesamte Anwendungslogik und jeden UI-State aufnimmt. Der richtige Einsatz von Pinia ist selektiv: Store-State für Daten, die mehrere Komponenten oder Routen teilen, die nach einer Seitennavigation erhalten bleiben sollen, oder die von Server-Events abhängen. Lokaler UI-State wie "ist dieses Dropdown offen" oder "ist diese Zeile in der Tabelle markiert" gehört nicht in Pinia — der Store würde damit überladen.
Die Pinia-Store-Struktur sollte funktional organisiert sein, nicht nach Komponenten. Ein Store pro fachlicher Domäne — useCartStore, useAuthStore, useNotificationStore — ist überschaubar und testbar. Wenn ein Store anfängt, State für mehr als eine fachliche Domäne zu halten — zum Beispiel sowohl Nutzerinformationen als auch Warenkorb — ist das ein Signal zur Aufteilung. Modulare Pinia-Stores mit klaren Domänengrenzen verhindern die Store-seitige State Explosion und machen die Anwendung navigierbar.
7. Props-down, Events-up: Datenfluss diszipliniert halten
Das Props-down, Events-up-Muster in Vue ist nicht nur ein Lehrbuchprinzip, sondern das wichtigste strukturelle Werkzeug gegen State Explosion. Wenn Daten ausschließlich von Eltern zu Kindern via Props fließen und Änderungswünsche ausschließlich via emit nach oben gemeldet werden, ist der Datenfluss in jeder Komponente lesbar: Der State kommt herein, die Komponente rendert ihn, Nutzerinteraktionen erzeugen Events. Keine Frage, wo der State gerade lebt — er lebt immer an einer einzigen Stelle und fließt nach unten.
Wenn dieses Muster gebrochen wird — beispielsweise durch direkte Mutation von Prop-Objekten oder durch globale State-Direktzugriffe aus tief verschachtelten Kind-Komponenten — entsteht ein Netz von impliziten Abhängigkeiten, das State Explosion fördet. Die Alternative zu tiefer Prop-Weitergabe ist nicht State-Explosion, sondern provide/inject für gemeinsame State-Bestandteile, die mehrere Ebenen tief gebraucht werden, oder ein Pinia-Store für wirklich anwendungsweiten State. Props-down, Events-up bleibt das Standardmuster; provide/inject und Pinia sind gezielte Ausnahmen für spezifische Szenarien.
8. Komponentenzerlegung als State-Komplexitäts-Reduzierer
Komponentenzerlegung in Vue ist das mächtigste Werkzeug gegen State Explosion — nicht weil sie State verschwinden lässt, sondern weil sie State in kleine, isolierte Einheiten aufteilt, die jede für sich verständlich sind. Eine Komponente mit dreißig reaktiven Variablen kann oft in fünf Komponenten aufgeteilt werden, die je sechs reaktive Variablen haben — jede mit einem klaren, einzelnen Verantwortungsbereich. Die Gesamtmenge an State mag dieselbe sein, aber die Komplexität pro Einheit ist drastisch reduziert.
Das Kriterium für eine sinnvolle Zerlegung: Wenn eine Gruppe von reaktiven Variablen immer zusammen geändert wird und zusammen im Template gerendert wird, gehören sie in eine eigene Komponente oder ein eigenes Composable. Wenn eine Komponente beginnt, State für UI-Elemente zu halten, die visuell und logisch abgegrenzt sind — ein Filterbereich, eine Ergebnisliste, ein Detailbereich — ist das ein klares Signal für die Zerlegung. Kleine, ruhige Komponenten mit wenigen reaktiven Variablen sind das Ziel; Zerlegung ist das Mittel, um dorthin zu gelangen.
9. State-Muster im Vergleich: reaktiv vs. abgeleitet vs. extern
| State-Muster | Werkzeug | Einsatz | Risiko bei Missbrauch |
|---|---|---|---|
| Lokaler UI-State | ref() |
isOpen, isLoading, activeTab | State Explosion bei zu vielen |
| Derived State | computed() |
Berechnungen aus Props/Store | Kaum — das richtige Werkzeug |
| Nebenwirkungen | watch() |
API-Aufrufe, externe Libs | State-Sync-Chaos bei Missbrauch |
| Geteilter App-State | Pinia Store | Warenkorb, Auth, Notifications | Store-Explosion wenn überladen |
| Hierarchie-State | provide/inject |
Headless-Bäume, Deep Props | Undurchsichtig bei Übernutzung |
| Reaktive Kopie von Prop | ref + watch | Fast nie sinnvoll | State Explosion, Inkonsistenz |
Die wichtigste Lektion aus dem Vergleich: Fast jeder Watcher, der State synchronisiert oder transformiert, ist ein Zeichen dafür, dass ein computed die richtigere Wahl gewesen wäre. State Explosion in Vue entsteht selten durch bewusste Entscheidungen — meistens durch schrittweise Erweiterungen, bei denen das einfachste kurzfristige Mittel (ein weiterer Watcher, eine weitere reactive Variable) die langfristige Komplexität erhöht. Die Gegenmaßnahme ist keine einmalige Refaktorierung, sondern eine permanente Design-Disziplin: Derived State via computed, lokaler State minimal, Pinia für geteilte Domänendaten.
Mironsoft
Vue.js Architekturberatung, State-Refactoring und Komponenten-Optimierung
Vue-Projekt mit State Explosion und unkontrollierter Komplexität?
Wir analysieren Vue-Anwendungen auf State-Komplexität, identifizieren falsch eingesetzte Watcher und reactive Kopien, und refaktorieren auf minimalen State mit sauberen computed-Ableitungen und Pinia-Strukturen.
State-Audit
Analyse auf unnötige reactive-Kopien, Watcher-Kaskaden und falsche State-Ownership
Refactoring
Watcher durch computed ersetzen, lokalen State minimieren, Pinia-Stores strukturieren
Architektur-Guidelines
State-Design-Regeln für das Team, die künftige State Explosion strukturell verhindern
10. Zusammenfassung
State Explosion in Vue entsteht durch reaktive Datenkopien von Props, Watcher die State synchronisieren statt Nebenwirkungen auszulösen, und durch das Fehlen klarer State-Ownership-Regeln. Das Gegenmittel ist ein systematischer Ansatz: Lokaler State beschränkt sich auf echten UI-State. Alles, was aus anderen Quellen berechenbar ist, wird als computed-Wert abgeleitet. Watcher sind nur für externe Nebenwirkungen reserviert. Geteilter Domänen-State lebt in modularen Pinia-Stores. Props fließen nach unten, Events nach oben.
Die Auswirkung ist direkt messbar: Komponenten mit minimalem State sind leichter lesbar, leichter testbar und leichter erweiterbar. Bugs durch State-Inkonsistenz — ein Nutzer sieht veraltete Daten, weil zwei State-Quellen nicht synchron sind — verschwinden, wenn der Grundsatz der Single Source of Truth konsequent umgesetzt wird. Kleine, ruhige Vue-Komponenten sind keine Utopie, sondern das Ergebnis bewusster State-Design-Entscheidungen, die konsequent von Beginn eines Features angewendet werden.
State Explosion verhindern — Das Wichtigste auf einen Blick
Lokaler State minimal
Nur echter UI-State reaktiv deklarieren — isOpen, isLoading, activeTab. Keine reaktiven Kopien von Props.
Derived State via computed
Alles, was aus Props oder Store abgeleitet werden kann, gehört in ein computed — nie in ein ref mit Watcher-Sync.
Watcher nur für Nebenwirkungen
API-Aufrufe, externe Bibliotheken, Persistenz. State-Synchronisation und -Transformation gehören in computed.
Pinia gezielt einsetzen
Nur geteilten Domänen-State in Pinia. UI-State bleibt lokal. Modulare Stores pro fachlicher Domäne verhindern Store-Explosion.