Pinia Anfänger-Tutorial
In Vue3 ist State-Management ein unumgängliches Thema.
Pinia ist eine leichtgewichtige State-Management-Bibliothek für Vue.js, mit der man State zwischen Komponenten teilen und verwalten kann. Wir können uns Pinia als ein globales Datenlager vorstellen, aus dem alle Komponenten Daten abrufen oder aktualisieren können.
In diesem Kapitel stellen wir Pinia vor, die offiziell empfohlene State-Management-Bibliothek für Vue3. Im Vergleich zu Vuex bietet Pinia eine einfachere State-Management-Lösung, die besser zur Denkweise der Vue3 Composition API passt.
Das folgende Diagramm vergleicht die strukturellen Unterschiede zwischen den beiden:
- Vuex verwendet einen einzigen Store mit mehrstufigen Modulen in einer Baumstruktur, mit fester Hierarchie, setzt auf Mutationen und ist insgesamt schwergewichtiger.
- Pinia hingegen besteht aus mehreren unabhängigen Stores; es ist flach, leichtgewichtig, hat keine Modultrennung und keinen Namespace-Overhead.

Kernfunktionen von Pinia:
- Unterstützt Vue2 und Vue3
- Minimalistisches API-Design
- Vollständige TypeScript-Unterstützung
- Unterstützt die Composition API
- Modulares Design ohne verschachtelte Module
Pinias flache Struktur ermöglicht es Komponenten, sich direkt mit jedem Store zu verbinden und sich zu nehmen, was sie brauchen, ohne über Baummodule oder Namespace-Pfade zu gehen. Der State-Fluss ist einfach und direkt, mit geringer Kopplung.

Installation und Konfiguration
Ein Projekt mit Vite erstellen:
npm create vite@latest vue-pinia-demo --template vue cd vue-pinia-demo npm install
Pinia installieren
Installiere zuerst Pinia in deinem Vue3-Projekt:
npm install pinia # 或者 yarn add pinia
Pinia besteht aus drei Teilen:
- state: speichert Daten
- getters: berechnet abgeleitete Daten
- actions: führt Logik aus, ändert state
Pinia konfigurieren
Pinia in src/main.js oder src/main.ts konfigurieren:
Beispiel
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
// Pinia-Instanz erstellen
const pinia = createPinia()
// Die Vue-App erstellen
const app = createApp(App)
// Pinia verwenden
app.use(pinia)
app.mount('#app')
Erstelle deinen ersten Store
Ein Store ist der Datenspeicher in Pinia. Lass uns einen einfachen Zähler-Store erstellen.
Einen Store definieren
Erstelle eine neue Datei src/stores/useCounter.js:
Beispiel
import { defineStore } from 'pinia'
// Definiere einen Store mit defineStore
// Der erste Parameter ist die eindeutige ID des Stores
// Der zweite Parameter enthält die Konfigurationsoptionen für den Store
export const useCounterStore = defineStore('counter', {
// state: definiert die Zustandsdaten des Stores
state: () => ({
count: 0,
name: 'My Counter'
}),
// getters: definiert berechnete Eigenschaften basierend auf dem State
Getter: {
doubleCount: (state) => state.count * 2,
// Verwende this, um auf andere Getter zuzugreifen
doubleCountPlusOne() {
return this.doubleCount + 1
}
},
// actions: definiert Methoden zum Ändern des Zustands
actions: {
increment() {
this.count++
},
decrement() {
this.count--
},
// Kann Parameter akzeptieren
incrementBy(amount) {
this.count += amount
},
// Asynchrone Aktion
async incrementAsync() {
// Asynchrone Operation simulieren
await new Promise(resolve => setTimeout(resolve, 1000))
this.count++
}
}
})
Erklärung der Store-Struktur
Verstehen wir die verschiedenen Teile eines Stores anhand einer Tabelle:
| Teil | Zweck | Beispiel |
|---|---|---|
| state | Definiert die gespeicherten Daten | count: 0 |
| Getter | Berechnete Eigenschaften basierend auf dem Zustand | doubleCount: state => state.count * 2 |
| actions | Methoden zum Ändern des Zustands | increment() { this.count++ } |
Verwendung des Stores in Komponenten
Grundlegende Verwendung
Der Komponentencode für src/components/CounterComponent.vue lautet wie folgt:
Beispiel
<template>
<div class="counter">
<h3>{{ store.name }}</h3>
<p>Aktueller Zählerstand: {{ store.count }}</p>
<p>Doppelter Zählerstand: {{ store.doubleCount }}</p>
<p>Doppelter Zählerstand plus eins: {{ store.doubleCountPlusOne }}</p>
<button @click="store.increment()">+1</button>
<button @click="store.decrement()">-1</button>
<button @click="store.incrementBy(5)">+5</button>
<button @click="store.incrementAsync()">Asynchron +1</button>
<button @click="reset">Zurücksetzen</button>
</div>
</template>
<script setup>
import { useCounterStore } from '../stores/useCounter'
// Den Store in setup verwenden
const store = useCounterStore()
// Methode zum Zurücksetzen des Zustands
function reset() {
store.$reset() // Die $reset-Methode kann den Zustand auf seinen Anfangswert zurücksetzen
}
</script>
Ändern Sie src/App.vue wie folgt:
Beispiel
import CounterComponent from './components/CounterComponent.vue'
</script>
<template>
<CounterComponent />
</template>
Die gesamte Projektstruktur:

Führen Sie den Befehlnpm run devaus, und besuchen Sie im Browserhttp://localhost:5173/, und sehen Sie sich den Effekt an:

Reaktive Destrukturierung
Wenn du State-Eigenschaften direkt im Template verwenden möchtest, kannst dustoreToRefs:
Beispiel
<div>
<p>Anzahl: {{ count }}</p>
<p>Name: {{ name }}</p>
</div>
</template>
<script setup>
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter'
const store = useCounterStore()
// Verwende storeToRefs, um die Reaktivität zu erhalten
const { count, name } = storeToRefs(store)
// Hinweis: Direktes Destrukturieren führt zum Verlust der Reaktivität!
// Falscher Weg: const { count, name } = store
</script>
Store im Composition-API-Stil
Pinia unterstützt auch das Definieren von Stores im Composition-API-Stil:
Beispiel
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useUserStore = defineStore('user', () => {
// Zustand
const user = ref(null)
const isLoggedIn = ref(false)
// Getter
const userName = computed(() => user.value?.name || 'Gast')
const userAge = computed(() => user.value?.age || 0)
// Aktionen
function login(userData) {
user.value = userData
isLoggedIn.value = true
}
function logout() {
user.value = null
isLoggedIn.value = false
}
return {
user,
isLoggedIn,
userName,
userAge,
login,
logout
}
})
Interaktion zwischen Stores
Mehrere Stores können sich gegenseitig aufrufen:
Beispiel
import { defineStore } from 'pinia'
import { useUserStore } from './user'
export const useCartStore = defineStore('cart', {
state: () => ({
items: []
}),
actions: {
addItem(product) {
this.items.push(product)
// Eine Aktion aus einem anderen Store aufrufen
const userStore = useUserStore()
if (userStore.isLoggedIn) {
// Mit dem Warenkorb des Benutzers synchronisieren
this.syncToUserCart()
}
}
}
})
Praktisches Beispiel: Warenkorb
Lassen Sie uns ein vollständiges Warenkorb-Beispiel erstellen:
Beispiel
export const useProductStore = defineStore('products', {
state: () => ({
products: [
{ id: 1, name: 'Laptop', price: 5999, stock: 10 },
{ id: 2, name: 'Smartphone', price: 3999, stock: 20 },
{ id: 3, name: 'Kabellose Ohrhörer', price: 299, stock: 50 }
]
}),
Getter: {
// Produkt nach ID abrufen
getProductById: (state) => (id) => {
return state.products.find(product => product.id === id)
},
// Produkte auf Lager abrufen
availableProducts: (state) => {
return state.products.filter(product => product.stock > 0)
}
}
})
Beispiel
export const useCartStore = defineStore('cart', {
state: () => ({
items: [], // { productId, quantity }
discount: 0
}),
Getter: {
// Gesamtzahl der Artikel im Warenkorb
totalItems: (state) => {
return state.items.reduce((total, item) => total + item.quantity, 0)
},
// Gesamtmenge des Warenkorbs
totalPrice: (state) => {
const productStore = useProductStore()
return state.items.reduce((total, item) => {
const product = productStore.getProductById(item.productId)
return total + (product?.price || 0) * item.quantity
}, 0)
},
// Gesamtpreis nach Rabatt
finalPrice: (state) => {
return state.totalPrice * (1 - state.discount / 100)
}
},
actions: {
// Produkt zum Warenkorb hinzufügen
addToCart(productId, quantity = 1) {
const existingItem = this.items.find(item => item.productId === productId)
if (existingItem) {
existingItem.quantity += quantity
} else {
this.items.push({ productId, quantity })
}
},
// Produkt aus dem Warenkorb entfernen
removeFromCart(productId) {
this.items = this.items.filter(item => item.productId !== productId)
},
// Warenkorb leeren
clearCart() {
this.items = []
this.discount = 0
},
// Rabatt festlegen
setDiscount(percent) {
this.discount = Math.max(0, Math.min(100, percent))
}
}
})
Den Warenkorb in einer Komponente verwenden
Beispiel
<template>
<div class="shopping-cart">
<h3>Warenkorb ({{ cart.totalItems }} Artikel)</h3>
<div v-if="cart.items.length === 0">
<p>Der Warenkorb ist leer</p>
</div>
<div v-else>
<div v-for="item in cartItems" :key="item.product.id" class="cart-item">
<span>{{ item.product.name }}</span>
<span>¥{{ item.product.price }}</span>
<span>Menge: {{ item.quantity }}</span>
<span>Zwischensumme: ¥{{ item.product.price * item.quantity }}</span>
<button @click="cart.removeFromCart(item.product.id)">Löschen</button>
</div>
<div class="cart-summary">
<p>Gesamt: ¥{{ cart.totalPrice }}</p>
<p v-if="cart.discount >0">Rabatt: {{ cart.discount }}%</p>
<p>Bezahlter Betrag: ¥{{ cart.finalPrice }}</p>
</div>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { useCartStore } from '@/stores/cart'
import { useProductStore } from '@/stores/products'
const cart = useCartStore()
const products = useProductStore()
// Details der Warenkorbartikel berechnen
const cartItems = computed(() => {
return cart.items.map(item => ({
product: products.getProductById(item.productId),
quantity: item.quantity
})).filter(item => item.product) // Produkte herausfiltern, die nicht existieren
})
</script>
Best Practices und Überlegungen
1. Namenskonventionen für Stores
Beispiel
export const useUserStore = defineStore('user', { /* ... */ })
export const useProductStore = defineStore('products', { /* ... */ })
// Zu vermeidende Benennung
export const userStore = defineStore('user', { /* ... */ }) // Fehlendes use-Präfix
2. Zustandsinitialisierung
Beispiel
state: () => ({
items: [],
loading: false,
error: null
})
// Nicht empfohlen: direkt ein Objekt verwenden
state: {
items: [] // Dies wird dazu führen, dass alle Instanzen dasselbe Array teilen!
}
3. Umgang mit asynchronen Operationen
Beispiel
async fetchUserData(userId) {
this.loading = true
this.error = null
try {
const response = await api.getUser(userId)
this.user = response.data
} catch (error) {
this.error = error.message
} finally {
this.loading = false
}
}
}
4. Datenpersistenz
Für Daten, die persistiert werden müssen, kannst du ein Plugin verwenden:
npm install pinia-plugin-persistedstate
Beispiel
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
export const useUserStore = defineStore('user', {
state: () => ({
Token: '',
userInfo: {}
}),
// Persistenz aktivieren
persist: true
})
Pinia API
| Kategorie | API | Beschreibung |
|---|---|---|
| Erstellen | defineStore |
Definiere einen unabhängigen Store, der State / Getter / Actions enthält |
| 实例化 | createPinia |
Erstelle eine Pinia-Root-Instanz für app.use() |
| Verwendung in Komponenten | useStore()(Benutzerdefiniert, z. B.useUserStore) |
Ruft einen Store auf, gibt reaktiven State, Getter und Actions zurück |
| State | state |
Gibt ein Funktionsobjekt zurück, das zum Definieren von veränderbarem globalen State verwendet wird |
| State-Operationen | store.$state |
Direktes Lesen/Schreiben des gesamten States (auf Objektebene) |
| State-Operationen | store.$patch() |
State batchweise ändern, unterstützt sowohl Objekt- als auch Funktionsmodi |
| State-Ersetzung | store.$reset() |
Zurücksetzen auf den initialen State, nur verfügbar, wenn er mit der setup-Methode definiert wurde |
| State-Abonnement | store.$subscribe() |
Auf State-Änderungen lauschen, geeignet für lokale Persistenz |
| Action-Aufruf | store.$onAction() |
Vor und nach Action-Aufrufen lauschen, kann für Logging und Tracking verwendet werden |
| Getter | getters |
Abgeleitete Daten, automatisch auf Basis des States zwischengespeichert |
| Plugins | pinia.use() |
Plugins registrieren, um die Fähigkeiten des Stores zu erweitern |
| Definitionsmethode | defineStore(id, options) |
Options-API-Syntax |
| Definitionsmethode | defineStore(id, () => {...}) |
Setup-Syntax, gibt State / Getter / Action zurück |
| Store-Eigenschaften | store.$id |
Eindeutige Kennung des aktuellen Stores |
| Store-Eigenschaften | store.$ready(Einige Versionen) |
Status-Flag nach abgeschlossener Store-Initialisierung |
| Persistenz (Plugin) | persist |
Storage-Plugins aktivieren (z. B. localStorage) |
| Hilfsfunktionen | storeToRefs() |
State / Getter in refs umwandeln, wobei die Reaktivität erhalten bleibt |
Weitere ErweiterungenFür weitere Informationen siehe die offizielle Pinia-Website:https://pinia.vuejs.org/