Pinia — Globale Zustandsverwaltung
In diesem Kapitel lernst du, Pinia zu verwenden, um gemeinsame Daten über Komponenten und Seiten hinweg zu verwalten, und die Funktion "Lieblingsartikel" zu implementieren.
Warum brauchen wir eine globale Zustandsverwaltung?
Bisher wurden Daten zwischen Komponenten über props und emit weitergegeben.
Das reicht für einfache Eltern-Kind-Szenarien aus, wird aber in den folgenden Situationen umständlich:
- Der Favoritenstatus muss zwischen der Startseite und der Detailseite synchronisiert werden.
- Der Benutzer-Anmeldestatus muss über alle Seiten hinweg geteilt werden.
- Warenkorbdaten müssen zwischen der Navigationsleiste, der Produktseite und der Checkout-Seite übergeben werden.
Weitergabe von Props Ebene für EbeneDer Schmerzpunkt: Wenn Daten in Komponente A an Komponente D übergeben werden müssen, müssen sie B und C als Relais passieren, obwohl diese Zwischenkomponenten die Daten selbst nicht benötigen.
PiniaPinia ist die von Vue 3 offiziell empfohlene State-Management-Bibliothek. Sie bietet ein globales "Datenzentrum", in dem jede Komponente Daten direkt lesen und ändern kann, ohne schichtweise Weitergabe.
Pinia installieren und registrieren
Wenn du beim Erstellen des Projekts bereits Pinia ausgewählt hast, überspringe die Installationsschritte.
Andernfalls:
$ npm install pinia
Registriere es in main.js:
Beispiel
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
const app = createApp(App)
// Eine Pinia-Instanz erstellen und registrieren
const pinia = createPinia()
app.use(pinia)
app.use(router)
app.mount('#app')
defineStore — Definieren eines Stores
Der Kern von Pinia ist derStoreJeder Store verwaltet eine unabhängige Zustandseinheit.
Es besteht aus drei Teilen:
| Teil | Zweck | Analogie |
|---|---|---|
| state | Store-Daten | Daten / ref der Komponente |
| Getter | Abgeleitete berechnete Daten | Computed der Komponente |
| actions | Methoden zum Ändern von Daten | Methoden / Funktionen der Komponente |
Beispiel
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
// defineStore('storeName', () => { ... })
// Der zweite Parameter verwendet eine Funktionssyntax, konsistent mit dem Composition-API-Stil.
export const useFavoriteStore = defineStore('favorite', () => {
// ====== State: State mit ref definieren ======
// Verwende ein Array, um die IDs der favorisierten Artikel zu speichern
const favoriteIds = ref([])
// Die Favoritenliste aus localStorage wiederherstellen
const saved = localStorage.getItem('blog-favorites')
if (saved) {
favoriteIds.value = JSON.parse(saved)
}
// ====== Getter: berechnete Eigenschaften mit computed definieren ======
const favoriteCount = computed(() => favoriteIds.value.length)
// Ermitteln, ob ein Artikel als Favorit markiert wurde
function isFavorite(articleId) {
return favoriteIds.value.includes(articleId)
}
// ====== Aktionen: Änderungsmethoden mit regulären Funktionen definieren ======
// Favoritenstatus umschalten (favorisiert → nicht favorisiert, nicht favorisiert → favorisiert)
function toggleFavorite(articleId) {
const index = favoriteIds.value.indexOf(articleId)
if (index === -1) {
favoriteIds.value.push(articleId) // Favorit hinzufügen
} else {
favoriteIds.value.splice(index, 1) // Favorit entfernen
}
// Mit localStorage synchronisieren (persistente Speicherung)
localStorage.setItem('blog-favorites', JSON.stringify(favoriteIds.value))
}
return { favoriteIds, favoriteCount, isFavorite, toggleFavorite }
})
Der zweite Parameter von defineStore kann auf zwei Arten geschrieben werden:Options Store(ähnlich der data/methods/computed-Objektsyntax von Vue 2) undSetup Store(eine Funktionssyntax unter Verwendung der Composition API). Es wird empfohlen, den Setup Store zu verwenden, der mit der
<script setup>Syntax übereinstimmt, die in Komponenten verwendet wird, und den geringsten Lernaufwand hat.
Verwendung des Stores in Komponenten
Jede Komponente kannuseFavoriteStore()eine Store-Instanz abrufen und dann direkt den State lesen/schreiben und Actions aufrufen.
Hinzufügen eines Favoriten-Buttons in BlogCard
Beispiel
<script setup>
import { useFavoriteStore } from '../stores/useFavoriteStore.js'
const props = defineProps({
id: Number,
title: String,
summary: String,
date: String,
category: String
})
const favoriteStore = useFavoriteStore()
// Wird aufgerufen, wenn auf die Favoriten-Schaltfläche geklickt wird
function handleFavorite(e) {
e.preventDefault() // Verhindert, dass RouterLink navigiert
favoriteStore.toggleFavorite(props.id)
}
</script>
<template>
<RouterLink :to="{ name: 'post', params: { id } }" class="card-link">
<div class="card">
<span class="tag">{{ category }}</span>
<h3>{{ title }}</h3>
<p>{{ summary }}</p>
<div class="card-footer">
<span class="date">{{ date }}</span>
<!-- Favoriten-Schaltfläche: ausgefüllt, wenn als Favorit markiert, umrandet, wenn nicht als Favorit markiert -->
<button class="fav-btn" @click="handleFavorite">
{{ favoriteStore.isFavorite(id) ? '♥' : '♡' }}
</button>
</div>
</div>
</RouterLink>
</template>
<style scoped>
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 10px;
}
.fav-btn {
background: none;
border: none;
font-size: 20px;
cursor: pointer;
color: #e74c3c;
padding: 4px 8px;
}
.fav-btn:hover {
transform: scale(1.2);
}
</style>
Füge außerdem einen Favoriten-Button auf der Detailseite hinzu
Beispiel
<script setup>
import { useFavoriteStore } from '../stores/useFavoriteStore.js'
const favoriteStore = useFavoriteStore()
// ... sonstiger Code
</script>
<template>
<article v-if="article">
<div class="post-header">
<h1>{{ article.title }}</h1>
<!-- Favoriten-Button auf der Detailseite: Zustand vollständig mit der Startseite synchronisiert -->
<button class="fav-btn" @click="favoriteStore.toggleFavorite(article.id)">
{{ favoriteStore.isFavorite(article.id) ? '♥Favorisiert' : '♡ Favorisieren' }}
</button>
</div>
<!-- ... -->
</article>
</template>
Der Favoritenstatus auf der Startseite und der Detailseite ist vollständig synchronisiert: Nachdem du einen Artikel auf der Startseite favorisiert hast, wird beim Aufrufen der Detailseite automatisch "Favorisiert" angezeigt.
Das ist der Wert der globalen Zustandsverwaltung: Daten werden an einem einzigen Ort gespeichert, und alle Komponenten lesen und schreiben dieselbe Quelle. Es ist keine emit-Weitergabe oder schichtweise Props-Weiterleitung nötig.
Anzeigen der Favoritenanzahl in NavBar
Zeige die Anzahl der Favoriten auf der rechten Seite der Navigationsleiste an; klicke darauf, um die Liste der favorisierten Artikel zu sehen.
Beispiel
<script setup>
import { useFavoriteStore } from '../stores/useFavoriteStore.js'
const favoriteStore = useFavoriteStore()
</script>
<template>
<header class="navbar">
<a href="/" class="logo">BEISPIEL Blog</a>
<nav>
<!-- Badge für Favoritenanzahl -->
<span class="fav-badge" v-if="favoriteStore.favoriteCount > 0">
Favoriten {{ favoriteStore.favoriteCount }}
</span>
</nav>
</header>
</template>
<style scoped>
.fav-badge {
background: #e74c3c;
color: #fff;
padding: 4px 10px;
border-radius: 12px;
font-size: 13px;
}
</style>
Die Anzahl in NavBar bleibt automatisch mit der neuesten Favoritenliste synchronisiert – das Favorisieren oder Entfernen eines Artikels aus den Favoriten ändert sofort die Zahl in der Navigationsleiste.
Zusammenfassung der drei Kernfähigkeiten von Pinia
| Fähigkeit | Entsprechender Code | Anwendung im Favoriten-Feature |
|---|---|---|
| Globales Teilen | Jede Komponente kann useXxxStore() verwenden | NavBar, BlogCard und PostView teilen dieselbe Favoritenliste. |
| Reaktivität | Wenn sich der Zustand ändert, werden alle Konsumenten automatisch aktualisiert. | Nach dem Favorisieren wird die Schaltfläche auf der Startseite rot, der Zähler in der Navigationsleiste erhöht sich um 1, und die Detailseite wird synchron aktualisiert. |
| Persistenz | In actions in localStorage schreiben | Die Favoritenliste existiert nach dem Aktualisieren der Seite weiterhin. |
Kapitelzusammenfassung
In diesem Kapitel haben Sie die vollständige Verwendung von Pinia für die globale Zustandsverwaltung gemeistert: Definieren eines Stores mit defineStore (state/getters/actions), Abrufen einer Instanz mit useXxxStore in Komponenten und Implementieren der Datenpersistenz durch localStorage.
Das Feature "Lieblingsartikel" macht die Datensynchronisierung zwischen Startseite, Detailseite und Navigationsleiste sehr natürlich, und alle Komponenten bleiben automatisch konsistent.
Andere Erweiterungen