Vue 3 Zustandsverwaltung für Admin-Panels: Pinia vs. lokal
Vue 3 Zustandsverwaltung für Admin-Panels: Wähle zwischen Pinia, provide/inject und lokalem Zustand anhand realer Admin-Beispiele wie Filter, Drafts und Tabs.

Was Zustand in Admin-Panels schwierig macht
Admin-Panels wirken zustandsintensiv, weil viele bewegliche Teile auf einer Oberfläche zusammenkommen. Eine Tabelle ist nicht nur Daten. Sie hat Sortierung, Filter, Pagination, ausgewählte Zeilen und den „was ist gerade passiert?“-Kontext, auf den Nutzer angewiesen sind. Fügst du lange Formulare, rollenbasierte Berechtigungen und Aktionen hinzu, die das erlaubte UI verändern, werden kleine Zustandsentscheidungen wichtig.
Die Herausforderung ist nicht, Werte zu speichern. Es geht darum, Verhalten vorhersehbar zu halten, wenn mehrere Komponenten dieselbe Wahrheit brauchen. Wenn ein Filter-Chip „Aktiv“ anzeigt, sollten Tabelle, URL und Export-Aktion übereinstimmen. Wenn ein Nutzer einen Datensatz bearbeitet und weg navigiert, darf die App seine Arbeit nicht unauffällig verlieren. Öffnet er zwei Tabs, sollte ein Tab den anderen nicht überschreiben.
In Vue 3 landet man normalerweise bei drei Orten, um Zustand zu halten:
- Lokaler Komponenten-Zustand: gehört einer Komponente und kann sicher zurückgesetzt werden, wenn sie unmounted wird.
provide/inject: geteilter Zustand, der auf eine Seite oder Feature-Area begrenzt ist, ohne Props durchzureichen.- Pinia: geteilter Zustand, der Navigation überdauern, über Routen wiederverwendet werden und leicht debuggt werden muss.
Eine nützliche Denkweise: Entscheide für jedes Stück Zustand, wo es leben soll, damit es korrekt bleibt, den Nutzer nicht überrascht und nicht zu Spaghetti führt.
Die Beispiele unten bleiben bei drei typischen Admin-Problemen: Filter und Tabellen (was soll persistieren vs. zurückgesetzt werden), Drafts und ungespeicherte Änderungen (Formulare, denen Nutzer vertrauen können) und Multi-Tab-Bearbeitung (Kollisionen vermeiden).
Eine einfache Klassifikation von Zustand vor der Tool-Wahl
State-Debatten werden leichter, wenn du aufhörst, über Tools zu streiten und zuerst benennst, welche Art von Zustand du hast. Unterschiedliche Zustandsarten verhalten sich anders, und ihr Vermischen erzeugt die seltsamen Bugs.
Eine praktische Aufteilung:
- UI-Zustand: Toggles, offene Dialoge, ausgewählte Zeilen, aktive Tabs, Sortierreihenfolge.
- Server-Zustand: API-Antworten, Lade-Flags, Fehler, letzte Aktualisierungszeit.
- Formularzustand: Feldwerte, Validierungsfehler, Dirty-Flags, ungespeicherte Drafts.
- Cross-Screen-Zustand: alles, was mehrere Routen lesen oder ändern müssen (aktueller Workspace, geteilte Berechtigungen).
Dann definiere den Scope. Frage, wo der Zustand heute verwendet wird, nicht wo er eines Tages gebraucht werden könnte. Wenn er nur innerhalb einer Tabellenkomponente wichtig ist, reicht lokaler Zustand meist aus. Brauchen zwei Geschwisterkomponenten auf derselben Seite den Zustand, ist das echte Problem das Teilen auf Seitenebene. Brauchen mehrere Routen den Zustand, befindest du dich im Bereich geteilten App-Zustands.
Als Nächstes kommt die Lebensdauer. Mancher Zustand soll beim Schließen einer Drawer zurückgesetzt werden. Anderer Zustand soll Navigation überdauern (Filter, während du in einen Datensatz klickst und zurückkehrst). Manche Zustände sollen ein Reload überstehen (ein langer Draft, zu dem Nutzer später zurückkehren). Alle drei gleich zu behandeln ist der Grund für Filter, die sich mysteriös zurücksetzen, oder Drafts, die verschwinden.
Schließlich prüfe Konkurrenz. Admin-Panels treffen schnell auf Edge-Cases: Ein Nutzer öffnet denselben Datensatz in zwei Tabs, ein Hintergrund-Refresh aktualisiert eine Zeile, während ein Formular dirty ist, oder zwei Bearbeiter konkurrieren beim Speichern.
Beispiel: eine „Nutzer“-Ansicht mit Filtern, einer Tabelle und einem Edit-Drawer. Filter sind UI-Zustand mit Seiten-Lifetime. Reihen sind Server-Zustand. Drawer-Felder sind Formularzustand. Wenn derselbe Nutzer in zwei Tabs bearbeitet wird, brauchst du eine explizite Concurrency-Entscheidung: blockieren, mergen oder warnen.
Sobald du Zustand nach Typ, Scope, Lebensdauer und Konkurrenz labeln kannst, wird die Tool-Wahl (lokal, provide/inject oder Pinia) meist deutlich klarer.
Wie man wählt: ein Entscheidungsprozess, der hält
Gute Zustandsentscheidungen beginnen mit einer Gewohnheit: Beschreibe den Zustand in einfachen Worten, bevor du ein Tool wählst. Admin-Panels mischen Tabellen, Filter, große Formulare und Navigation zwischen Datensätzen, daher kann schon „kleiner“ Zustand zu Bug-Quellen werden.
Ein 5-Schritte-Entscheidungsprozess
-
Wer braucht den Zustand?
- Eine Komponente: lokal halten.
- Mehrere Komponenten unter einer Seite:
provide/injecterwägen. - Mehrere Routen: Pinia in Betracht ziehen.
Filter sind ein gutes Beispiel. Wenn sie nur eine Tabelle steuern, ist lokaler Zustand in Ordnung. Leben Filter in einer Header-Komponente, die die Tabelle unten steuert, ist Page-Scoped-Sharing (oft
provide/inject) sauberer. -
Wie lange muss er leben?
- Wenn er verschwinden kann, wenn die Komponente unmountet, ist lokaler Zustand ideal.
- Muss er einen Routenwechsel überdauern, ist Pinia oft besser geeignet.
- Muss er einen Reload überstehen, brauchst du zusätzlich Persistenz (Storage), unabhängig vom Ort, an dem er liegt.
Das ist besonders wichtig für Drafts. Ungespeicherte Änderungen sind vertrauenssensitiv: Nutzer erwarten häufig, dass ein Draft noch da ist, wenn sie wegklicken und zurückkehren.
-
Soll er über Browser-Tabs geteilt sein oder pro Tab isoliert bleiben?
Multi-Tab-Bearbeitung ist ein Ort, an dem Bugs sich verstecken. Wenn jeder Tab seinen eigenen Draft haben soll, vermeide ein globales Singleton. Bevorzuge nach Record-ID gekeyte Zustände oder behalte ihn seitenbegrenzt, sodass ein Tab den anderen nicht überschreibt.
-
Wähle die einfachste Option, die passt.
Starte lokal. Hebe an, wenn echter Schmerz aufkommt: Prop-Drilling, duplizierte Logik oder schwer reproduzierbare Resets.
-
Bestätige deine Debugging-Bedürfnisse.
Wenn du eine klare, inspectierbare Sicht auf Änderungen über Bildschirme brauchst, können Pinias zentralisierte Actions und State-Inspection Stunden sparen. Ist der Zustand kurzlebig und offensichtlich, ist lokaler Zustand leichter zu lesen.
Lokaler Komponenten-Zustand: wann er reicht
Lokaler Zustand ist die Default-Wahl, wenn die Daten nur einer Komponente auf einer Seite etwas bedeuten. Es ist leicht, diese Option zu überspringen und einen Store zu überbauen, den du monatelang pflegen musst.
Ein klar passender Fall ist eine einzelne Tabelle mit eigenen Filtern. Wenn die Filter nur diese Tabelle betreffen (zum Beispiel die Nutzerliste) und nichts anderes davon abhängt, halte sie als ref-Werte innerhalb der Tabellenkomponente. Dasselbe gilt für kleines UI-Zustand wie „ist das Modal offen?“, „welche Zeile wird bearbeitet?“ oder „welche Elemente sind derzeit ausgewählt?“.
Versuche nicht zu speichern, was du berechnen kannst. Das Badge „Aktive Filter (3)“ sollte aus den aktuellen Filterwerten computed werden. Sort-Labels, formatierte Zusammenfassungen und „kann speichern“-Flags sind ebenfalls besser als computed-Werte, weil sie automatisch synchron bleiben.
Reset-Regeln sind wichtiger als das gewählte Tool. Entscheide, was beim Routenwechsel gelöscht wird (normalerweise alles) und was bleibt, wenn der Nutzer innerhalb derselben Seite die Ansicht wechselt (du behältst möglicherweise Filter, aber löschst temporäre Auswahlen, um überraschende Massenaktionen zu vermeiden).
Lokaler Zustand reicht in der Regel, wenn:
- Der Zustand ein Widget betrifft (ein Formular, eine Tabelle, ein Modal).
- Keine andere Seite ihn lesen oder ändern muss.
- Du ihn innerhalb von 1–2 Komponenten halten kannst, ohne Props durch viele Schichten zu reichen.
- Du sein Reset-Verhalten in einem Satz beschreiben kannst.
Die Hauptgrenze ist Tiefe. Wenn du denselben Zustand durch mehrere verschachtelte Komponenten ‚durchreichst‘, wird lokaler Zustand zu Prop-Drilling – das ist meist dein Zeichen, zu provide/inject oder einem Store zu wechseln.
provide/inject: Zustand innerhalb einer Seite oder Feature-Area teilen
provide/inject sitzt zwischen lokalem Zustand und einem vollen Store. Ein Parent „providet“ Werte an alles darunter, und verschachtelte Komponenten „injecten“ sie, ohne Props zu durchlaufen. In Admin-Panels ist das eine großartige Lösung, wenn der Zustand zu einer einzelnen Seite oder Feature-Area gehört, nicht zur gesamten App.
Ein übliches Muster ist ein Page-Shell, das den Zustand besitzt, während kleinere Komponenten ihn konsumieren: ein Filter-Bar, die Tabelle, eine Bulk-Actions-Toolbar, ein Detail-Drawer und ein „ungespeicherte Änderungen“-Banner. Die Shell kann eine kleine reaktive Oberfläche bereitstellen wie ein filters-Objekt, ein draftStatus-Objekt (dirty, saving, error) und ein paar Read-Only-Flags (z. B. isReadOnly basierend auf Berechtigungen).
Was man bereitstellen sollte (klein halten)
Wenn du alles bereitstellst, hast du im Grunde einen Store mit weniger Struktur nachgebaut. Provide nur, was mehrere Kinder wirklich brauchen. Filter sind ein klassisches Beispiel: Wenn Tabelle, Chips, Export-Aktion und Pagination synchron bleiben müssen, ist eine gemeinsame Quelle der Wahrheit besser als Props und Events zu jonglieren.
Klarheit und Fallstricke
Das größte Risiko sind versteckte Abhängigkeiten: Ein Kind „funktioniert einfach“, weil oben etwas bereitgestellt wird, und später ist schwer zu sagen, woher Updates stammen.
Um es lesbar und testbar zu halten, gib Injections klare Namen (oft mit Konstanten oder Symbols). Bevorzuge auch, Actions zu providen, nicht nur veränderliche Objekte. Eine kleine API wie setFilter, markDirty und resetDraft macht Ownership und erlaubte Änderungen explizit.
Pinia: geteilter Zustand und vorhersehbare Updates über Bildschirme
Pinia spielt seine Stärken aus, wenn derselbe Zustand über Routen und Komponenten hinweg konsistent bleiben muss. In einem Admin-Panel bedeutet das oft: der aktuelle Nutzer, seine Berechtigungen, welche Organisation/Workspace ausgewählt ist und App-weite Einstellungen. Das wird schmerzhaft, wenn jede Ansicht das neu implementiert.
Ein Store hilft, weil er einen Ort bietet, geteilten Zustand zu lesen und zu aktualisieren. Statt Props durch mehrere Schichten zu reichen, importierst du den Store dort, wo du ihn brauchst. Wenn du von einer List zur Detailseite wechselst, kann der Rest der UI weiterhin auf denselben ausgewählten Workspace, Berechtigungen und Einstellungen reagieren.
Warum Pinia leichter zu warten wirkt
Pinia fördert eine einfache Struktur: state für Rohwerte, getters für abgeleitete Werte und actions für Updates. In Admin-UIs verhindert diese Struktur, dass „Quick-Fixes“ zu verstreuten Mutationen werden.
Wenn canEditUsers vom aktuellen Role-Setting plus einem Feature-Flag abhängt, pack die Regel in einen Getter. Wenn das Wechseln der Organisation das Leeren gecachter Selektionen und das Neuladen der Navigation erfordert, setze die Sequenz in eine Action. So entstehen weniger mysteriöse Watcher und weniger „warum hat sich das geändert?“-Momente.
Pinia funktioniert auch gut mit Vue DevTools. Tritt ein Bug auf, ist es viel einfacher, den Store-Zustand zu inspizieren und zu sehen, welche Action ausgeführt wurde, als Änderungen über beliebige reaktive Objekte in zufälligen Komponenten zu jagen.
Vermeide den Mülleimer-Store
Ein globaler Store wirkt anfangs ordentlich, wird dann aber zur Schublade für alles Mögliche. Gute Kandidaten für Pinia sind wirklich geteilte Belange wie User-Identität und Berechtigungen, ausgewählter Workspace, Feature-Flags und geteilte Referenzdaten, die auf vielen Bildschirmen gebraucht werden.
Seitenseitige Belange (wie temporäre Inputs eines einzelnen Formulars) sollten lokal bleiben, sofern nicht mehrere Routen sie wirklich brauchen.
Beispiel 1: Filter und Tabellen, ohne alles zum Store zu machen
Stell dir eine Orders-Seite vor: Tabelle, Filter (Status, Datumsbereich, Kunde), Pagination und ein Seitenpanel, das die ausgewählte Bestellung vorschaut. Das wird schnell unordentlich, weil es verlockend ist, jeden Filter und jede Einstellung in einen globalen Store zu legen.
Eine einfache Entscheidung ist, was erinnert werden soll und wo:
- Nur Erinnerung (lokal oder provide/inject): wird gelöscht, wenn du die Seite verlässt. Gut für Wegwerf-Zustand.
- Query-Parameter: teilbar und überlebt Reload. Gut für Filter und Pagination, die Nutzer kopieren wollen.
- Pinia: überdauert Navigation. Gut, wenn Nutzer „zur Liste zurückkehren wollen, genau wie sie sie verlassen haben.“
Von dort folgt meist die Implementierung:
Wenn niemand erwartet, dass Einstellungen Navigation überdauern, halte filters, sort, page und pageSize in der Orders-Page-Komponente und lass diese Komponente das Fetch auslösen. Brauchen Toolbar, Tabelle und Preview-Panel dasselbe Modell und wird Prop-Drilling laut, verschiebe das List-Model in die Page-Shell und teile es via provide/inject. Soll die Liste sticky über Routen hinweg sein (öffne eine Order, spring weg, komm zurück und sie soll gleich bleiben), ist Pinia die bessere Wahl.
Eine praktische Regel: starte lokal, geh zu provide/inject, wenn mehrere Kinder dasselbe Modell brauchen, und greife nur dann zu Pinia, wenn du wirklich Cross-Route-Persistenz brauchst.
Beispiel 2: Drafts und ungespeicherte Änderungen (Formulare, denen Nutzer vertrauen)
Stell dir einen Support-Agenten vor, der einen Kunden-Datensatz bearbeitet: Kontaktdaten, Abrechnung und interne Notizen. Er wird unterbrochen, wechselt die Ansicht und kommt zurück. Vergisst das Formular seine Arbeit oder speichert halbfertige Daten, ist Vertrauen weg.
Für Drafts trenne drei Dinge: den zuletzt gespeicherten Datensatz, die gestagten Änderungen des Nutzers und UI-only-Zustand wie Validierungsfehler.
Lokaler Zustand: gestagte Änderungen mit klaren Dirty-Regeln
Wenn der Edit-Screen eigenständig ist, ist lokaler Komponenten-Zustand oft am sichersten. Halte eine draft-Kopie des Datensatzes, tracke isDirty (oder eine Field-Level-Dirty-Map) und speichere Fehler neben den Formcontrols.
Ein einfacher Flow: Record laden, in einen Draft klonen, Draft editieren und erst beim Klick auf Save eine Save-Request senden. Cancel verwirft den Draft und lädt neu.
provide/inject: ein Draft, geteilt über verschachtelte Sektionen
Admin-Formulare sind oft in Tabs oder Panels aufgeteilt (Profil, Adressen, Berechtigungen). Mit provide/inject kannst du ein Draft-Modell behalten und eine kleine API wie updateField(), resetDraft() und validateSection() bereitstellen. Jede Sektion liest und schreibt am selben Draft, ohne Props durch fünf Schichten zu reichen.
Wann Pinia bei Drafts hilft
Pinia ist nützlich, wenn Drafts Navigation überdauern oder außerhalb des Edit-Pfads sichtbar sein müssen. Ein gängiges Muster ist draftsById[customerId], sodass jeder Record seinen eigenen Draft hat. Das hilft auch, wenn Nutzer mehrere Edit-Screens gleichzeitig öffnen.
Draft-Bugs kommen meist von vorhersehbaren Fehlern: Draft erstellen, bevor der Record geladen ist; einen dirty Draft beim Refetch überschreiben; Fehler beim Cancel nicht löschen; oder einen einzigen Shared-Key verwenden, der Drafts überschreibt. Wenn du klare Regeln setzt (wann erstellen, überschreiben, verwerfen, persistieren und nach Save ersetzen), verschwinden die meisten Probleme.
Wenn du Admin-Screens mit AppMaster baust, gilt die Trennung „Draft vs. gespeicherter Datensatz“ weiterhin: behalte den Draft auf dem Client und betrachte das Backend erst nach einem erfolgreichen Save als Quelle der Wahrheit.
Beispiel 3: Multi-Tab-Bearbeitung ohne Zustandskollisionen
Multi-Tab-Bearbeitung ist einer der Punkte, an denen Admin-Panels oft scheitern. Ein Nutzer öffnet Kunde A, dann Kunde B, wechselt hin und her und erwartet, dass jeder Tab seine ungespeicherten Änderungen erinnert.
Die Lösung ist, jeden Tab als eigenes State-Bündel zu modellieren, nicht als einen gemeinsamen Draft. Jeder Tab braucht mindestens einen eindeutigen Key (oft basiert auf Record-ID), die Draft-Daten, Status (clean, dirty, saving) und Feld-Fehler.
Leben die Tabs in einer einzigen Seite, funktioniert ein lokaler Ansatz gut. Halte die Tab-Liste und Drafts in der Page-Komponente, die die Tabs rendert. Jeder Editor-Panel liest und schreibt nur sein eigenes Bündel. Schließt ein Tab, lösche das Bündel und fertig. Das hält Dinge isoliert und leicht verständlich.
Unabhängig vom Speicherort ist die Struktur ähnlich:
- Eine Liste von Tab-Objekten (jeweils mit
customerId,draft,statusunderrors) - Ein
activeTabKey - Actions wie
openTab(id),updateDraft(key, patch),saveTab(key)undcloseTab(key)
Pinia ist die bessere Wahl, wenn Tabs Navigation überdauern müssen (zu Orders springen und zurück) oder wenn mehrere Screens Tabs öffnen und fokussieren sollen. Dann hält ein kleiner „Tab-Manager“-Store Verhalten in der ganzen App konsistent.
Die Hauptkollision, die es zu vermeiden gilt, ist eine einzelne globale Variable wie currentDraft. Sie funktioniert, bis der zweite Tab geöffnet wird; dann überschreiben sich Änderungen, Validierungsfehler erscheinen am falschen Ort und Save aktualisiert den falschen Datensatz. Wenn jeder offene Tab sein eigenes Bündel hat, verschwinden Kollisionen meist durch Design.
Häufige Fehler, die Bugs und unordentlichen Code erzeugen
Die meisten Admin-Panel-Bugs sind keine „Vue“-Bugs. Es sind State-Bugs: Daten leben am falschen Ort, zwei Teile der Ansicht stimmen nicht überein oder alter Zustand bleibt heimlich bestehen.
Hier die Muster, die am häufigsten auftreten:
Alles standardmäßig in Pinia zu legen macht Ownership unklar. Ein globaler Store wirkt ordentlich, aber bald liest und schreibt jede Seite dieselben Objekte und Cleanup wird Ratespiel.
provide/inject ohne klares Contract erzeugt versteckte Abhängigkeiten. Wenn ein Kind filters injectt, aber es keine gemeinsame Vereinbarung gibt, wer das liefert und welche Actions Änderungen vornehmen dürfen, bekommst du Überraschungs-Updates, wenn ein anderes Kind dasselbe Objekt mutiert.
Server-Zustand und UI-Zustand im selben Store zu mischen führt zu versehentlichen Überschreibungen. Gefetchte Records verhalten sich anders als „ist Drawer offen?“, „aktueller Tab“ oder „dirty fields“. Leben sie zusammen, kann ein Refetch das UI plattmachen, oder UI-Änderungen mutieren gecachte Daten.
Lifecycle-Cleanup zu überspringen lässt Zustand leaken. Filter von einer Ansicht können eine andere beeinflussen und Drafts können nach dem Verlassen der Seite bleiben. Beim nächsten Öffnen eines anderen Datensatzes sieht man alte Selektionen und denkt, die App sei kaputt.
Drafts schlecht zu keyen ist ein stiller Vertrauens-Killer. Wenn du Drafts unter einem Key wie draft:editUser speicherst, überschreibt das Editieren von User A später das Draft von User B.
Eine einfache Regel verhindert die meisten Probleme: Halte Zustand so nah wie möglich an dem Ort, wo er genutzt wird, und heb ihn nur an, wenn zwei unabhängige Teile ihn wirklich teilen müssen. Wenn du teilst, definiere Ownership (wer darf ändern) und Identity (wie wird es gekeyed).
Eine kurze Checkliste, bevor du lokal, provide/inject oder Pinia wählst
Die nützlichste Frage ist: Wer besitzt diesen Zustand? Wenn du es nicht in einem Satz sagen kannst, macht der Zustand wahrscheinlich zu viel und sollte aufgeteilt werden.
Nutze diese Checks als schnellen Filter:
- Kannst du den Owner benennen (eine Komponente, eine Seite oder die ganze App)?
- Muss er Routenwechsel oder Reloads überdauern? Wenn ja, plane Persistenz statt auf den Browser zu hoffen.
- Können zwei Datensätze gleichzeitig bearbeitet werden? Wenn ja, keye Zustand nach Record-ID.
- Wird der Zustand nur von Komponenten unter einer Page-Shell verwendet? Wenn ja, passt oft
provide/inject. - Musst du Änderungen inspizieren und verstehen, wer was wann geändert hat? Wenn ja, ist Pinia oft der sauberste Ort für dieses Slice.
Tool-Matching in klaren Worten:
Wenn Zustand innerhalb einer Komponente lebt und stirbt (z. B. ein Dropdown offen/geschlossen), halte ihn lokal. Wenn mehrere Komponenten derselben Seite Kontext teilen (Filter-Bar + Tabelle + Summary), ist provide/inject passend. Muss Zustand über Bildschirme geteilt werden, Navigation überdauern oder vorhersehbare, debuggbare Updates bieten, greife zu Pinia und keye Einträge nach Record-ID, wenn Drafts involviert sind.
Wenn du eine Vue 3 Admin-UI baust (auch eine, die mit Tools wie AppMaster generiert wurde), hilft diese Checkliste, zu vermeiden, alles zu früh in einen Store zu legen.
Nächste Schritte: Zustand entwickeln, ohne Chaos zu schaffen
Der sicherste Weg, Zustandsmanagement in Admin-Panels zu verbessern, ist, es in kleinen, langweiligen Schritten zu wachsen. Starte mit lokalem Zustand für alles, was innerhalb einer Seite bleibt. Wenn du echte Wiederverwendung siehst (kopierte Logik, eine dritte Komponente braucht denselben Zustand), heb ihn eine Ebene an. Ziehe erst dann einen Shared Store in Betracht.
Ein für die meisten Teams funktionierender Pfad:
- Halte seitenlokalen Zustand zuerst lokal (Filter, Sort, Pagination, offene/geschlossene Panels).
- Nutze
provide/inject, wenn mehrere Komponenten auf derselben Seite geteilten Kontext benötigen. - Füge Pinia-Store nach Bedarf hinzu, je ein Store für Cross-Screen-Needs (Draft-Manager, Tab-Manager, aktueller Workspace).
- Schreibe Reset-Regeln und halte dich daran (was bei Navigation, Logout, Clear Filters, Discard Changes zurückgesetzt wird).
Reset-Regeln klingen klein, verhindern aber die meisten „warum hat sich das geändert?“-Momente. Entscheide z. B., was mit einem Draft passiert, wenn jemand einen anderen Datensatz öffnet und zurückkommt: wiederherstellen, warnen oder zurücksetzen. Mach dieses Verhalten konsistent.
Wenn du einen Store einführst, halte ihn feature-shaped. Ein Drafts-Store sollte Erstellen, Wiederherstellen und Löschen von Drafts behandeln, aber nicht gleichzeitig Table-Filter oder UI-Layout-Flags besitzen.
Wenn du schnell ein Admin-Panel prototypen willst, kann AppMaster (appmaster.io) eine Vue3-Web-App plus Backend und Geschäftslogik generieren; du kannst den generierten Code dort verfeinern, wo spezielles Zustandsverhalten nötig ist. Ein praktischer nächster Schritt ist, einen Screen End-to-End zu bauen (z. B. ein Edit-Formular mit Draft-Recovery) und zu sehen, was wirklich Pinia braucht und was lokal bleiben kann.
FAQ
Verwende lokalen Zustand, wenn die Daten nur eine Komponente betreffen und beim Unmount dieser Komponente zurückgesetzt werden können. Typische Beispiele sind Dialoge (öffnen/schließen), ausgewählte Zeilen in einer einzigen Tabelle und ein Formabschnitt, der anderswo nicht wiederverwendet wird.
Nutze provide/inject, wenn mehrere Komponenten auf derselben Seite eine gemeinsame Quelle der Wahrheit benötigen und Prop-Drilling zu unübersichtlich wird. Halte das, was du bereitstellst, klein und gezielt, damit die Seite leicht zu verstehen bleibt.
Verwende Pinia, wenn Zustand über Routen hinweg geteilt werden muss, Navigation überdauern soll oder du Änderungen an einer zentralen Stelle gut inspizieren und debuggen möchtest. Übliche Beispiele sind aktueller Workspace, Berechtigungen, Feature-Flags und cross-screen „Manager“ wie Drafts oder Tabs.
Beginne damit, den Typ zu benennen (UI, Server, Formular, cross-screen), dann bestimme Scope (eine Komponente, eine Seite, viele Routen), Lifetime (beim Unmount zurücksetzen, Navigation überdauern, Reload überdauern) und Concurrency (ein Editor oder mehrere Tabs). Aus diesen vier Labels folgt meist die TOOL-Wahl.
Wenn Nutzer die Ansicht teilen oder wiederherstellen sollen, packe Filter und Pagination in Query-Parameter, damit sie Reloads überstehen und kopierbar sind. Wenn Nutzer vor allem „zur Liste zurückkehren, wie ich sie verlassen habe“ erwarten, ist Pinia passend; ansonsten reicht Seitenscope.
Trenne das zuletzt gespeicherte Record von den vom Nutzer gestagten Änderungen und schreibe nur bei Save zurück. Verfolge eine klare Dirty-Regel und entscheide, was bei Navigation passiert (warnen, auto-save oder wiederherstellbarer Draft), damit Nutzer nichts verlieren.
Gib jedem offenen Editor-Bereich sein eigenes State-Bündel, das nach Record-ID (und manchmal einem Tab-Key) gekeyed ist, statt eine einzige globale currentDraft-Variable zu nutzen. So überschreiben sich Änderungen und Validierungsfehler nicht zwischen Tabs.
Eine seitenseitige provide/inject-Lösung funktioniert, wenn der gesamte Edit-Flow auf einer Route bleibt. Müssen Drafts Routenwechsel überdauern oder außerhalb des Edit-Screens verfügbar sein, ist Pinia mit draftsById[recordId] in der Regel einfacher und vorhersehbarer.
Speichere nicht, was du berechnen kannst. Leite Badges, Zusammenfassungen und "can save"-Flags aus dem aktuellen Zustand mit computed-Werten ab, damit sie nicht aus dem Takt geraten.
Alles standardmäßig in Pinia zu legen, Server-Antworten mit UI-Toggles zu vermischen oder beim Navigations-Lifecycle aufzuräumen zu versäumen sind die häufigsten Fehler. Achte außerdem auf schlechte Keys wie einen geteilten Draft-Key, der für verschiedene Records wiederverwendet wird.


