Start / Cheat Sheets

Cheat Sheet

Vue 3 Anwendung — Cheat Sheet

Stand: · Vue.js Grundlagen & Composition API

Vue.jsPiniaVue RouterDeployment

Der Spickzettel für alles, was aus einzelnen Komponenten eine Anwendung macht: Komponenten verbinden, navigieren, Daten holen, Zustand teilen, ausliefern. Die Komponente selbst — Template, Reaktivität, Formulare, Styling — steht auf dem ersten Blatt: Vue 3 Kern.

Maßgeblich sind die offiziellen Dokumentationen zu Vue, Vue Router und Pinia; dieses Blatt ist eine Auswahl für den Alltag.

Datenfluss: welcher Weg wofür

WegRichtungPassend für
PropsEltern zu KindDaten und Konfiguration
EreignisseKind zu ElternAbsichten melden
provide / injectüber EbenenKontext für einen Teilbaum
SlotsInhalt hineinStruktur von außen
Storeüberallechter Anwendungszustand

Der schnellste Weg ist selten der richtige: provide löst kein Prop-Problem, und ein Store für Zustand, den nur zwei Komponenten betreffen, ist zu viel. Ereignisse steigen nicht auf — nur die direkte Elternkomponente hört zu.

Modul: Komponentenkommunikation

Props deklarieren und prüfen

defineProps({
  name:      { type: String, required: true },
  ausleihen: { type: Number, default: 0 },
  zustand:   { type: String, validator: (w) => ['frei', 'weg'].includes(w) },
  daten:     { type: Object, default: () => ({}) }   // Objekt-Default als Funktion!
})
OptionBedeutung
typeerwarteter Typ; mehrere als Array [String, Number]
requiredPflichtangabe
defaultVorgabewert — bei Objekt/Array Funktion
validatoreigene Regel, gibt true zurück
StelleSchreibweise
DeklarationcamelCase (Pflicht)
Übergabe im Templatekebab-case üblich
Nutzung im Kind-TemplatecamelCase

Im Produktionsbau prüft Vue nicht mehr — Validierung ist Entwicklungshilfe, kein Schutz zur Laufzeit. Ohne Doppelpunkt ist der übergebene Wert eine Zeichenkette, auch bei einer Zahl. Ein nicht deklariertes Attribut landet als Fallthrough am Wurzelelement.

Modul: Komponentenkommunikation

Ereignisse

<script setup>
const emit = defineEmits(['erhoehen'])
</script>
<template><button @click="emit('erhoehen', 1)">Mehr</button></template>

<!-- Verwendung -->
<MengenFeld @erhoehen="(n) => menge += n" />
Form von defineEmitsNutzen
Array von Namenknapp, keine Prüfung
Objekt mit Funktionenprüft die Nutzdaten, warnt bei false
TypeScript-SignaturenTypen für Namen und Argumente

Ereignisnamen in camelCase melden, im Template kebab-case hören — Vue rechnet um. Undeklarierte Ereignisse funktionieren, landen aber zusätzlich in den Fallthrough-Attributen.

Modul: Komponentenkommunikation

Lifecycle-Reihenfolge im Baum

PhaseReihenfolgeFolge
setupaußen nach innenEltern kennen Kinder noch nicht
onMountedinnen nach außenKinder stehen zuerst
Aktualisierungaußen nach innenProps erreichen Kinder danach
onUnmountedinnen nach außenKinder räumen zuerst auf

Ein Template-Ref ist erst in onMounted belegt; auf ein Kind in setup zuzugreifen schlägt fehl. Wer in onMounted lädt, muss damit rechnen, dass Kinder zuerst eine leere Liste sehen — sie müssen beides vertragen.

Modul: Komponentenkommunikation

provide und inject

// Anbieter: readonly nach außen, die ändernde Funktion mitgeben
provide('thema', { thema: readonly(thema), wechsle })
// Nachfahre
const { thema, wechsle } = inject('thema')
SchreibweiseBedeutung
provide('name', wert)Zeichenkette als Schlüssel
provide(schluessel, wert)Symbol aus einer Datei — kollisionssicher
inject('name', 'vorgabe')Vorgabewert bei Fehlen
inject(k, () => neu(), true)Vorgabe aus einer Fabrik
app.provide(…)app-weit, in main.js

Fehlt der Anbieter, gibt es keine Warnung — nur undefined, und der Fehler zeigt sich weit entfernt. Angemessen für Kontext (Thema, Sprache, Konfiguration eines Teilbaums); für Anwendungszustand ist ein Store der bessere Weg.

Modul: Komponentenkommunikation

Slots

<!-- Ersatzinhalt -->
<button class="knopf"><slot>Absenden</slot></button>

<!-- Scoped Slot: Daten heraus, Darstellung bleibt außen -->
<li v-for="a in artikel" :key="a.id"><slot :artikel="a" :frei="a.verfuegbar" /></li>
<ArtikelListe v-slot="{ artikel }">{{ artikel.name }}</ArtikelListe>

<!-- Benannte Slots, mit Prüfung gegen leere Rahmen -->
<header v-if="$slots.kopf"><slot name="kopf" /></header>
SchreibweiseBedeutung
v-slot:kopfLangform
#kopfKurzform (braucht immer ein Argument)
#defaultStandard-Slot ausdrücklich
#kopf="{ wert }"benannt mit Slot-Props
#[name]dynamischer Slotname

Slot-Inhalt gehört zur Elternkomponente und trägt deren scoped-Kennung. Ein Tippfehler im Slotnamen bleibt stumm — der Inhalt erscheint einfach nicht.

Modul: Komponentenkommunikation

Vue Router

const routes = [
  { path: '/', component: ArtikelListe },
  { path: '/artikel/:id', component: Detail, name: 'artikel' },
  { path: '/:pathMatch(.*)*', component: Fehlt }   // Auffangroute zuletzt!
]
export default createRouter({ history: createWebHistory(), routes })
// main.js
createApp(App).use(router).mount('#app')

Die Liste wird der Reihe nach geprüft — eine Auffangroute an falscher Stelle verschluckt alles Folgende. Ohne <RouterView /> bleibt die Seite leer, ohne dass ein Fehler erscheint.

useRouteruseRoute
Liefertdie Router-Instanzdie aktuelle Route
ZwecknavigierenParameter lesen
Typischpush, replaceparams, query
Reaktivneinja
paramsquery
In der Adresse/artikel/7?sortierung=name
Deklaration nötigja, im Pfadnein
Typimmer Zeichenketteimmer Zeichenkette
Passend fürIdentität der RessourceFilter und Sortierung
KlasseWann sie gesetzt wird
router-link-activeRoute passt, auch als übergeordnete
router-link-exact-activenur bei exakter Übereinstimmung

Ein Link auf / trägt die aktive Klasse sonst auf fast jeder Seite. push legt einen Historieneintrag an, replace ersetzt den aktuellen.

Parameterwechsel erzeugt nichts neu: Bei gleicher Zielkomponente verwendet der Router die Instanz weiter, die Lifecycle-Hooks laufen kein zweites Mal. Darauf reagiert man mit onBeforeRouteUpdate oder einem Watcher — und lädt beim ersten Aufruf zusätzlich direkt, denn der Wächter läuft dort nicht.

ModusSchreibweiseWofür
booleschprops: trueparams direkt als Props (nicht query)
Objektprops: { a: 1 }feste Werte
Funktionprops: (r) => …umwandeln und kombinieren

Die Funktionsform ist der Ort, an dem aus der Zeichenkette eine Zahl wird — nicht in der Komponente.

StrategieAdresseServer nötig
createWebHistory/artikel/7ja, Auffangregel
createWebHashHistory/#/artikel/7nein, aber SEO-Notlösung
createMemoryHistorykeinenein, für Tests und SSR

Modul: Routing und Navigation

HTTP: fetch, Axios, Fehlerzustände

Jeder Abruf hat drei Zustände — wer nur den Erfolgsfall baut, hat zwei Drittel vergessen: laedt, daten, fehler.

const antwort = await fetch('/api/artikel')
if (!antwort.ok) throw new Error(`HTTP ${antwort.status}`)   // 500 lässt fetch NICHT scheitern
return antwort.json()                                        // selbst asynchron
fetchAxios
Installationkeineein Paket
JSONselbst umwandelnautomatisch
Fehlerstatusselbst prüfenwirft von allein
Grundadresseselbst bauenin der Instanz
Abfangfunktionenselbst schreibeneingebaut

Bei Axios stehen die Nutzdaten unter data, nicht direkt im Ergebnis.

FehlerartUrsacheReaktion
Netzwerkoffline, ZeitüberschreitungWiederholung anbieten
400erAnfrage falschEingaben zeigen
401 / 403nicht angemeldetzur Anmeldung
500erServerfehlermelden, später erneut
SchichtDateiKennt
Umgebung.envnichts
Clientapi/client.jsdie Grundadresse
Ressourceapi/artikel.jsden Client
Zustandcomposables/useAbruf.jsdie Ressource
Anzeigeviews/ArtikelListe.vuedas Composable

Kein Sprung über eine Schicht hinweg, sonst hilft die Aufteilung nicht mehr.

WerkzeugZweck
watchauf Änderung reagieren
watchEffectAbhängigkeiten selbst erkennen
onWatcherCleanupvor dem nächsten Lauf aufräumen (ab Vue 3.5)
AbortControllerlaufende Anfrage abbrechen
EntprellungAnfragen bündeln statt jede zu senden

Ohne Abbruch schreibt die langsamere Antwort das falsche Ergebnis — ein Wettlauf, der lokal fast nie auftritt und wie ein Anzeigefehler aussieht. Reaktive Argumente eines Composables über toValue normalisieren, sonst friert es auf dem ersten Wert ein.

Modul: HTTP und API-Integration

Pinia

Art von ZustandOrt
Formulareingabe, auf-/zugeklapptKomponente
Thema, Sprache eines Teilbaumsprovide / inject
Angemeldeter NutzerStore
Geladene Artikel, WarenkorbStore

Im Zweifel unten anfangen — nach oben wandern ist leicht, zurück selten.

// Setup-Store: wie eine Komponente, Privates möglich
export const useAusleihe = defineStore('ausleihe', () => {
  const offen = ref([])
  const anzahl = computed(() => offen.value.length)
  function hinzufuegen(a) { offen.value.push(a) }
  return { offen, anzahl, hinzufuegen }   // nur Zurückgegebenes ist sichtbar
})
Options-StoreSetup-Store
Aufbauvertraut aus Vuexwie eine Komponente
Privatesnicht möglichja
Composables nutzenumständlichdirekt
Watcher im Storeneinja
$reset()vorhandenselbst schreiben

Beim Options-Store ist state eine Funktion, kein Objekt — sonst teilen sich Instanzen den Zustand. Gegenüber Vuex entfallen die Mutationen; Actions ändern den Zustand direkt.

// falsch: verliert die Reaktivität
const { offen, anzahl } = useAusleihe()
// richtig
const store = useAusleihe()
const { offen, anzahl } = storeToRefs(store)   // Zustand und Getter
const { hinzufuegen } = store                  // Actions direkt, sind an nichts gebunden
Weg der ÄnderungWofür
store.feld = werteinzelne Zuweisung
store.$patch({ … })mehrere Felder in einem Schritt
store.$patch((s) => …)Änderungen an Listen
store.$reset()zurück zum Anfangszustand (nur Options-Store)
Actionfachlich benannter Vorgang

Einen anderen Store innerhalb der Action aufrufen, nicht im Modulkopf — dort läuft er vor der Anmeldung von Pinia. Getter mit Argument geben eine Funktion zurück und verlieren dabei das Gedächtnis; Asynchrones gehört in eine Action.

Mehrere kleine Stores schlagen einen großen: nutzer, artikel, ausleihe, meldungen. Ein Store hält seinen Zustand bis zum Neuladen der Seite — auch nach dem Abmelden.

Modul: State Management mit Pinia

Eigene Direktiven

Erst die Prüfreihenfolge, dann die Direktive:

AnliegenWerkzeug
Markup wiederverwendenKomponente
Zustandslogik teilenComposable
Elementverhalten teilenDirektive
Nur AussehenCSS-Klasse

Eine Direktive, die Daten liefert, ist in Wahrheit ein Composable; eine, die Markup erzeugt, eine Komponente. Sinnvolle Fälle brauchen das Element selbst: v-fokus, v-klick-aussen, v-sichtbar, v-tooltip.

const vFokus = { mounted: (el) => el.focus() }   // Variable vFokus → v-fokus im Template
const vFarbe = (el, binding) => { el.style.color = binding.value }  // Kurzform = mounted + updated
app.directive('fokus', vFokus)                   // global: ohne v im Namen, vor mount
binding-FeldInhalt
valueder übergebene Wert
oldValuevorheriger Wert, nur in beforeUpdate / updated
argdas Argument nach dem Doppelpunkt
modifiersObjekt der gesetzten Modifikatoren
instancedie Komponente, in der die Direktive steht

Hooks: created, beforeMount, mounted, beforeUpdate, updated, beforeUnmount, unmounted — alle optional. Für Listener am document ist beforeUnmount der richtige Ort, und der Handler muss benannt am Element liegen (el._h), sonst lässt er sich nicht entfernen. Auf Komponenten wirken Direktiven nur am Wurzelelement; bei mehreren Wurzeln werden sie ignoriert.

Modul: Eigene Direktiven

Build, Umgebung, Deployment

npm run build     # nach dist/
npm run preview   # dist lokal ausliefern, üblich Port 4173 — Prüfhilfe, kein Server
EntwicklungProduktion
Prop-Validierungprüft und warntentfällt
Warnungenausführlichkeine
Devtoolsverbundenabgeschaltet
FehlermeldungenKlartextFehlernummer
.env-DateiWann geladen
.envimmer
.env.localimmer, nicht ins Repository
.env.[modus]nur im genannten Modus (hat Vorrang)
.env.[modus].localnur im Modus, nicht ins Repository

Nur Variablen mit VITE_-Präfix erreichen den Anwendungscode — und das ist eine Sicherheitsentscheidung: Alles im Frontend ist für jeden Besucher lesbar. Echte Geheimnisse gehören auf den Server. Werte sind immer Zeichenketten, auch true und Zahlen; Änderungen wirken erst nach einem Neustart des Dev-Servers.

vite.config.jsWofür
baseBetrieb in einem Unterverzeichnis (mit Schrägstrich am Ende)
resolve.aliaskurze Importpfade wie @/components
build.outDiranderer Zielordner als dist
server.proxyHerkunftsprobleme beim Entwickeln umgehen (nur Dev)
build.sourcemapFehlersuche im Bau — macht den Originalcode lesbar
location / { try_files $uri $uri/ /index.html; }
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . index.html [L]

Nach dieser Regel meldet der Server nie mehr 404 — die Auffangroute in der Anwendung wird damit Pflicht. Der Fehler zeigt sich nie im Dev-Server, immer erst nach dem Deployment.

DateiCaching
assets mit Hash im Namenlange gültig, unveränderlich
index.htmlnie zwischenspeichern
Dateien aus publickurz, je nach Zweck

Ein zwischengespeichertes index.html verweist auf Bündel, die es nicht mehr gibt — die Seite bleibt weiß, ohne Serverfehler.

Prüfliste vor dem Ausliefern: Auffangregel, 404-Route, app.config.errorHandler (vor mount), Bündelgröße angesehen, keine Geheimnisse mit VITE_, index.html ohne Cache.

Modul: Deployment und Produktion

Typische Fallen

  • Zerlegter Store ohne storeToRefs aktualisiert sich nie wieder — der häufigste Pinia-Fehler.
  • props: true reicht nur params durch, nicht query; die Prop muss genauso heißen wie der Parameter im Pfad.
  • Routen-Parameter sind Zeichenketten — ein Vergleich mit einer Zahl schlägt fehl.
  • fetch scheitert bei 500 nicht. ok immer selbst prüfen; json() wirft bei leerem Rumpf, und eine Antwort lässt sich nur einmal auslesen.
  • catch, das nur auf die Konsole schreibt, ist keine Fehlerbehandlung. Ein abgebrochener Abruf löst ebenfalls ein catch aus und ist kein Fehler.
  • Ohne finally bleibt die Anzeige nach einem Fehler dauerhaft im Ladezustand.
  • Objekt-Defaults als Literal teilen sich alle Instanzen — in eine Funktion packen. Gilt für Props wie für state.
  • Fehlendes provide liefert stumm undefined, keine Warnung.
  • Nach app.mount registriert wirkt nichts mehr: weder Komponenten noch Direktiven noch Plugins.
  • Der Dev-Proxy täuscht über fehlende Serverkonfiguration hinweg.
  • base ohne abschließenden Schrägstrich führt zu falschen Pfaden; base und Router müssen zusammenpassen.
  • Ein API-Schlüssel mit VITE_-Präfix steht im ausgelieferten JavaScript.

Module: alle sechs des Anwendungs-Teils

Zum Seminar Vue.js Grundlagen & Composition API