Zum Inhalt springen

Pinia

Pinia ist die offizielle State-Management-Lösung für Vue 3. Es ersetzt Vuex und bietet eine einfachere, typsichere API.

Kernkonzepte:

  • State - Zentrale Datenhaltung
  • Getters - Berechnete Werte
  • Actions - Methoden & Async
Terminal-Fenster
npm install pinia
main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.mount('#app')
stores/counter.js
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
// State
const count = ref(0)
// Getters
const doubleCount = computed(() => count.value * 2)
// Actions
function increment() {
count.value++
}
async function incrementAsync() {
await new Promise(r => setTimeout(r, 1000))
count.value++
}
return { count, doubleCount, increment, incrementAsync }
})
stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
name: 'Counter'
}),
getters: {
doubleCount: (state) => state.count * 2,
// Getter mit Parametern
countPlusN: (state) => (n) => state.count + n
},
actions: {
increment() {
this.count++
},
async fetchAndSet() {
const data = await api.fetchCount()
this.count = data.count
}
}
})
AspektSetup SyntaxOptions Syntax
Stateref(), reactive()state: () => ({})
Getterscomputed()getters: {}
ActionsFunktionenactions: {}
TypeScriptAutomatischMehr Boilerplate
ComposablesDirekt nutzbarIn Actions
StilComposition APIOptions API
<script setup>
import { useCounterStore } from '@/stores/counter'
// Store-Instanz
const counterStore = useCounterStore()
// Direkter Zugriff
console.log(counterStore.count)
console.log(counterStore.doubleCount)
// Actions aufrufen
function handleClick() {
counterStore.increment()
}
</script>
<template>
<p>Count: {{ counterStore.count }}</p>
<p>Double: {{ counterStore.doubleCount }}</p>
<button @click="counterStore.increment()">+1</button>
</template>
<script setup>
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter'
const counterStore = useCounterStore()
// FALSCH: Reaktivität geht verloren!
const { count, doubleCount } = counterStore
// RICHTIG: storeToRefs für State & Getters
const { count, doubleCount } = storeToRefs(counterStore)
// Actions können direkt destrukturiert werden
const { increment, incrementAsync } = counterStore
</script>
<template>
<p>{{ count }}</p>
<button @click="increment">+1</button>
</template>
<script setup>
import { useCounterStore } from '@/stores/counter'
const store = useCounterStore()
// Direkte Mutation
store.count++
// Mit $patch (Objekt) - effizienter bei mehreren Änderungen
store.$patch({
count: store.count + 1,
name: 'New Name'
})
// Mit $patch (Funktion) - für komplexe Mutationen
store.$patch((state) => {
state.items.push({ id: 1, name: 'Item' })
state.count++
})
// State komplett ersetzen
store.$reset() // Zurück zum Initialzustand
</script>
stores/auth.js
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
import { authApi } from '@/api/auth'
export const useAuthStore = defineStore('auth', () => {
const user = ref(null)
const token = ref(localStorage.getItem('token'))
const isLoggedIn = computed(() => !!token.value)
const userName = computed(() => user.value?.name ?? 'Gast')
async function login(credentials) {
const response = await authApi.login(credentials)
token.value = response.token
user.value = response.user
localStorage.setItem('token', response.token)
}
function logout() {
token.value = null
user.value = null
localStorage.removeItem('token')
}
return { user, token, isLoggedIn, userName, login, logout }
})
<script setup>
import { useAuthStore } from '@/stores/auth'
import { useRouter } from 'vue-router'
const auth = useAuthStore()
const router = useRouter()
async function handleLogin(credentials) {
try {
await auth.login(credentials)
router.push({ name: 'dashboard' })
} catch (error) {
console.error('Login fehlgeschlagen:', error)
}
}
function handleLogout() {
auth.logout()
router.push({ name: 'home' })
}
</script>
<template>
<div v-if="auth.isLoggedIn">
<p>Willkommen, {{ auth.userName }}!</p>
<button @click="handleLogout">Logout</button>
</div>
</template>
stores/cart.js
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
import { useAuthStore } from './auth'
export const useCartStore = defineStore('cart', () => {
const items = ref([])
const total = computed(() =>
items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
)
async function checkout() {
const auth = useAuthStore() // Anderen Store verwenden
if (!auth.isLoggedIn) {
throw new Error('Bitte einloggen')
}
await api.checkout({
userId: auth.user.id,
items: items.value
})
items.value = []
}
return { items, total, checkout }
})
<script setup>
import { useCounterStore } from '@/stores/counter'
import { watch } from 'vue'
const store = useCounterStore()
// Mit $subscribe - alle Mutationen
store.$subscribe((mutation, state) => {
console.log('Mutation:', mutation.type)
console.log('Neuer State:', state)
// z.B. in localStorage speichern
localStorage.setItem('counter', JSON.stringify(state))
})
// Oder mit watch auf spezifische Werte
watch(
() => store.count,
(newCount) => {
console.log('Count geändert:', newCount)
}
)
</script>
  • Mutations + Actions
  • Komplexer Boilerplate
  • Schwache TypeScript-Unterstützung
  • Globale Module
  • commit(), dispatch()
mutations: {
SET_COUNT(state, value) {
state.count = value
}
},
actions: {
increment({ commit }) {
commit('SET_COUNT', ...)
}
}

Pinia integriert sich nahtlos in die Vue DevTools:

Features:

  • Store-State inspizieren
  • Getters anzeigen
  • Actions verfolgen
  • Time-Travel Debugging
  • State bearbeiten
// Stores benennen für bessere DevTools-Anzeige
defineStore('auth', () => { ... })
defineStore('cart', () => { ... })
defineStore('products', () => { ... })
  • Ein Store pro Domain/Feature
  • Setup Syntax für Konsistenz
  • storeToRefs() für Destrukturierung
  • Stores in stores/ Ordner
  • Actions für async Logik
  • Computed für abgeleitete Werte
  • Alles in einen Store
  • State direkt in Templates mutieren
  • Stores in Composables cachen
  • Zirkuläre Store-Abhängigkeiten
  • State ohne Actions ändern (außer einfache Fälle)

Offizielle Dokumentation:

Ressourcen: