Warum Pinia?
Globales State-Management in Vue 3 – und warum Props-Drilling irgendwann nicht mehr reicht.
Das Problem: State zwischen Komponenten teilen
In kleinen Apps reichen props und emit völlig aus. Aber sobald mehrere Ebenen tief verschachtelte Komponenten auf dieselben Daten zugreifen müssen, wird es schnell unübersichtlich.
<!-- App.vue -->
<template>
<Header :user="user" />
<Sidebar :user="user"> <!-- braucht user gar nicht -->
<UserProfile :user="user" /> <!-- braucht user wirklich -->
</Sidebar>
</template>
<!-- Sidebar.vue – leitet user nur durch -->
<template>
<nav>
<slot /> <!-- user muss als Prop durchgeschleift werden -->
</nav>
</template> Die Sidebar braucht den User gar nicht – sie reicht ihn nur durch. Bei drei, vier Ebenen wird das schnell unhaltbar.
Drei Ansätze im Vergleich
🔽 Props Drilling
Daten von Eltern an Kind weitergeben – einfach, aber skaliert nicht. Jede Zwischenkomponente muss Props durchschleifen.
📡 Events nach oben
Kinder emittieren Events – Eltern reagieren. Funktioniert für direkte Eltern-Kind-Beziehungen, aber nicht quer durch den Komponentenbaum.
🏪 Globaler Store
Ein zentraler Speicher, auf den jede Komponente direkt zugreifen kann. Genau das ist Pinia.
Datenfluss mit Pinia
┌─────────────────────────────────────────────┐
│ Pinia Store │
│ │
│ state: { user, cart, theme } │
│ getters: { cartTotal, isLoggedIn } │
│ actions: { login(), addToCart() } │
└──────────┬──────────┬──────────┬────────────┘
│ │ │
┌─────▼──┐ ┌────▼───┐ ┌──▼──────┐
│ Header │ │ Sidebar│ │ CartPage│
│ │ │ │ │ │
│ user ✓ │ │ cart ✓ │ │ cart ✓ │
│ theme ✓│ │ theme ✓│ │ total ✓ │
└────────┘ └────────┘ └─────────┘
Jede Komponente liest direkt aus dem Store.
Keine Props nötig – alles reaktiv.Pinia vs. Vuex – Warum Pinia gewonnen hat
Vuex war der offizielle State-Manager für Vue 2. Pinia wurde als leichtgewichtige Alternative entwickelt und ist seit Vue 3 die offizielle Empfehlung.
| Feature | Vuex | Pinia |
|---|---|---|
| Mutations | Pflicht (commit) | Keine – direkt ändern ✅ |
| TypeScript | Nachgerüstet, umständlich | Von Grund auf typisiert ✅ |
| DevTools | Ja | Ja (besser integriert) ✅ |
| Composition API | Umständlich | Nativer Support ✅ |
| Boilerplate | Viel (mutations, actions, …) | Minimal ✅ |
| Bundle-Größe | ~10 KB | ~1.5 KB ✅ |
Klare Empfehlung
Vuex 5 wird nicht mehr kommen. Pinia ist Vuex 5. Für neue Projekte gibt es keinen Grund mehr, Vuex zu verwenden.
Kernkonzepte von Pinia
🏪 Store
Ein reaktiver Container für zusammengehörigen State. Du definierst einen Store pro Domäne: useUserStore, useCartStore, useThemeStore.
📦 State
Die eigentlichen Daten – reaktive Variablen im Store. Vergleichbar mit data() in der Options API.
🔍 Getters
Berechnete Werte basierend auf dem State – wie computed(). Werden automatisch gecacht und nur bei Änderung neu berechnet.
⚡ Actions
Funktionen, die den State verändern – synchron oder asynchron. Hier gehört deine Business-Logik hin: API-Calls, Validierungen, Berechnungen.
Installation & Setup
In Nuxt 3 bereits integriert
Nuxt 3 hat ein offizielles Pinia-Modul. Du musst Pinia nicht manuell in die App einbinden – das Modul erledigt alles automatisch.
# Pinia + Nuxt-Modul installieren
npx nuxi module add pinia
# Oder manuell:
npm install pinia @pinia/nuxt// nuxt.config.ts
export default defineNuxtConfig({
modules: [
'@pinia/nuxt', // Pinia automatisch konfiguriert
],
}) Das war's. Kein createPinia(), kein app.use() – das Nuxt-Modul erledigt das automatisch. Du kannst sofort Stores definieren.
Für Rails-Entwickler
Rails-Vergleich: State-Management
In Rails hast du mehrere Orte für „State":
- Session/Cookies – User-bezogener State über Requests hinweg
- Redis/Memcached – Geteilter Cache zwischen Prozessen
- ActiveRecord – Persistenter State in der Datenbank
- Instance-Variablen – State innerhalb eines Requests (
@user)
Pinia ist am ehesten vergleichbar mit Instance-Variablen im Controller: globaler Zugriff innerhalb einer „Session" (hier: der Seiten-Lebenszyklus im Browser). Der Store lebt so lange wie die App im Browser – ähnlich wie eine Rails-Session, aber client-seitig.
# Rails: Globaler State über verschiedene Mechanismen
# ───────────────────────────────────────────────
# Session (Browser-bezogen):
session[:current_user_id] = user.id
# Cache (prozessübergreifend):
Rails.cache.write("user_#{id}", user_data)
# Controller Instance-Variable (Request-bezogen):
class ApplicationController < ActionController::Base
before_action :set_current_user
def set_current_user
@current_user = User.find(session[:current_user_id])
# → Vergleichbar mit: const user = useUserStore()
end
end
# Pinia-Äquivalent:
# ─────────────────
# const userStore = useUserStore()
# userStore.user → wie @current_user
# userStore.login() → wie set_current_user
# userStore.isLoggedIn → wie @current_user.present?Dein erster Store – Vorschau
So sieht ein minimaler Pinia-Store aus. Im nächsten Kapitel gehen wir das Schritt für Schritt durch.
// stores/counter.ts
export const useCounterStore = defineStore('counter', () => {
// State (wie ref() in Composition API)
const count = ref(0)
// Getter (wie computed())
const doubleCount = computed(() => count.value * 2)
// Action (normale Funktion)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})Setup-Syntax bevorzugt
Wir verwenden in diesem Tutorial die Setup-Syntax für Stores (Composition API Stil). Sie ist flexibler, besser typisiert und konsistent mit <script setup> in Komponenten.