Appearance
Kapitel 34: Pinia (Teil 2) - State, Getters & Actions
📙 Lernziel: State, Getters & Actions in Pinia meistern!
34.1 State (Zustand)
State ist der reaktive Zustand des Stores.
Beispiel:
javascript
// stores/counter.js
import { defineStore } from 'pinia'
import { ref, reactive } from 'vue'
export const useCounterStore = defineStore('counter', () => {
// State (mit ref)
const count = ref(0)
// State (mit reactive)
const user = reactive({
name: 'Max',
age: 25
})
return {
count,
user
}
})Verwendung in Komponente:
vue
<script setup>
import { useCounterStore } from '../stores/counter'
const counterStore = useCounterStore()
</script>
<template>
<div>
<p>Zähler: {{ counterStore.count }}</p>
<p>User: {{ counterStore.user.name }}</p>
<!-- State ändern -->
<button @click="counterStore.count++">+</button>
<button @click="counterStore.user.age++">Alter erhöhen</button>
</div>
</template>Wichtig:
- ✅ State ist read/write (kann direkt geändert werden)
- ✅ Besser: Actions verwenden für komplexe Logik
34.2 Getters (Berechnete Eigenschaften)
Getters sind berechnete Eigenschaften (wie computed()).
Beispiel:
javascript
// stores/counter.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const todos = ref([
{ id: 1, text: 'Lernen', done: false },
{ id: 2, text: 'Sport', done: true }
])
// Getters (computed)
const doubleCount = computed(() => count.value * 2)
const doneTodos = computed(() => todos.value.filter(todo => todo.done))
const undoneTodos = computed(() => todos.value.filter(todo => !todo.done))
return {
count,
todos,
doubleCount,
doneTodos,
undoneTodos
}
})Verwendung in Komponente:
vue
<script setup>
import { useCounterStore } from '../stores/counter'
const counterStore = useCounterStore()
</script>
<template>
<div>
<p>Zähler: {{ counterStore.count }}</p>
<p>Doppelt: {{ counterStore.doubleCount }}</p>
<p>Erledigt: {{ counterStore.doneTodos.length }}</p>
<p>Offen: {{ counterStore.undoneTodos.length }}</p>
</div>
</template>Vorteile:
- ✅ Caching (wird nur neu berechnet wenn Abhängigkeiten sich ändern)
- ✅ Wie
computed()in Vue
34.3 Actions (Aktionen)
Actions sind Funktionen, die State ändern oder asynchrone Operationen ausführen.
Beispiel:
javascript
// stores/counter.js
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const isLoading = ref(false)
// Actions (Funktionen)
const increment = () => {
count.value++
}
const decrement = () => {
count.value--
}
const reset = () => {
count.value = 0
}
const incrementBy = (value) => {
count.value += value
}
// Asynchrone Action
const fetchData = async () => {
isLoading.value = true
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts/1')
const data = await response.json()
console.log('Daten geladen:', data)
} catch (error) {
console.error('Fehler:', error)
} finally {
isLoading.value = false
}
}
return {
count,
isLoading,
increment,
decrement,
reset,
incrementBy,
fetchData
}
})Verwendung in Komponente:
vue
<script setup>
import { useCounterStore } from '../stores/counter'
const counterStore = useCounterStore()
</script>
<template>
<div>
<p>Zähler: {{ counterStore.count }}</p>
<button @click="counterStore.increment()">+</button>
<button @click="counterStore.decrement()">-</button>
<button @click="counterStore.reset()">Zurücksetzen</button>
<button @click="counterStore.incrementBy(5)">+5</button>
<button @click="counterStore.fetchData()">
{{ counterStore.isLoading ? 'Lädt...' : 'Daten laden' }}
</button>
</div>
</template>Best Practices:
- ✅ Komplexe Logik in Actions kapseln
- ✅ Asynchrone Operationen (API-Aufrufe) in Actions
- ✅ State nur in Actions ändern (nicht direkt in Komponente)
34.4 $reset() - Store zurücksetzen
$reset() setzt Store auf Ausgangswerte zurücksetzen.
Beispiel:
javascript
// stores/counter.js
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const user = ref({ name: '', age: 0 })
const reset = () => {
count.value = 0
user.value = { name: '', age: 0 }
}
return {
count,
user,
reset
}
})Verwendung:
vue
<script setup>
import { useCounterStore } from '../stores/counter'
const counterStore = useCounterStore()
</script>
<template>
<div>
<p>Zähler: {{ counterStore.count }}</p>
<button @click="counterStore.count++">+</button>
<button @click="counterStore.reset()">Zurücksetzen</button>
</div>
</template>Hinweis:
- ❌ In Setup Store gibt es kein automatisches
$reset(). Du musst es selbst implementieren. - ✅ In Options Store gibt es automatisches
$reset().
34.5 $patch() - State in Batches ändern
$patch() ändert mehrere State-Eigenschaften auf einmal (effizienter).
Beispiel:
javascript
// Stores/counter.js (Options Store)
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
user: {
name: 'Max',
age: 25
}
}),
actions: {
increment() {
this.count++
}
}
})Verwendung:
vue
<script setup>
import { useCounterStore } from '../stores/counter'
const counterStore = useCounterStore()
</script>
<template>
<div>
<p>Zähler: {{ counterStore.count }}</p>
<p>User: {{ counterStore.user.name }}, {{ counterStore.user.age }}</p>
<!-- Einzelne Änderung -->
<button @click="counterStore.count++">+</button>
<!-- Mehrere Änderungen auf einmal (effizienter) -->
<button @click="counterStore.$patch({ count: 0, user: { name: 'Anna', age: 30 } })">
Alles zurücksetzen
</button>
<!-- Mit Funktion (für komplexe Logik) -->
<button @click="counterStore.$patch((state) => {
state.count = 0
state.user.name = 'Tom'
state.user.age = 40
})">
Alles zurücksetzen (Funktion)
</button>
</div>
</template>Vorteile:
- ✅ Effizienter (nur ein Re-render)
- ✅ Bessere Performance bei mehreren Änderungen
34.6 $subscribe() - State-Änderungen überwachen
$subscribe() erlaubt es, auf State-Änderungen zu reagieren (z.B. für LocalStorage-Persistenz).
Beispiel:
javascript
// stores/counter.js
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
// State in LocalStorage persistieren
const storedCount = localStorage.getItem('count')
if (storedCount) {
count.value = JSON.parse(storedCount)
}
// Änderungen überwachen und in LocalStorage speichern
const unsubscribe = $subscribe((mutation, state) => {
console.log('Mutation:', mutation.type)
console.log('Neuer State:', state)
localStorage.setItem('count', JSON.stringify(state.count))
})
const increment = () => {
count.value++
}
return {
count,
increment
}
})Verwendung:
vue
<script setup>
import { useCounterStore } from '../stores/counter'
const counterStore = useCounterStore()
// Beim Unmounten unsubscribe (Cleanup)
onUnmounted(() => {
counterStore.$dispose() // Entfernt alle Subscriber
})
</script>
<template>
<div>
<p>Zähler: {{ counterStore.count }}</p>
<button @click="counterStore.increment()">+</button>
<p>(Wert wird in LocalStorage gespeichert!)</p>
</div>
</template>Anwendungsfälle:
- ✅ State in LocalStorage persistieren
- ✅ Analytics/Logging bei State-Änderungen
- ✅ Synchronisation mit Server
34.7 Übung: Todo Store mit Pinia (Erweitert)
Aufgabe: Erstelle einen Todo Store mit State, Getters und Actions.
Lösung:
1. Store erstellen (stores/todo.js):
javascript
// stores/todo.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useTodoStore = defineStore('todo', () => {
// State
const todos = ref([
{ id: 1, text: 'Lernen', done: false },
{ id: 2, text: 'Sport', done: true }
])
const filter = ref('all') // 'all', 'done', 'undone'
// Getters
const doneTodos = computed(() => todos.value.filter(todo => todo.done))
const undoneTodos = computed(() => todos.value.filter(todo => !todo.done))
const filteredTodos = computed(() => {
if (filter.value === 'done') {
return doneTodos.value
} else if (filter.value === 'undone') {
return undoneTodos.value
}
return todos.value
})
// Actions
const addTodo = (text) => {
todos.value.push({
id: Date.now(),
text,
done: false
})
}
const removeTodo = (id) => {
todos.value = todos.value.filter(todo => todo.id !== id)
}
const toggleTodo = (id) => {
const todo = todos.value.find(t => t.id === id)
if (todo) {
todo.done = !todo.done
}
}
const setFilter = (newFilter) => {
filter.value = newFilter
}
const reset = () => {
todos.value = []
filter.value = 'all'
}
return {
todos,
filter,
doneTodos,
undoneTodos,
filteredTodos,
addTodo,
removeTodo,
toggleTodo,
setFilter,
reset
}
})2. Komponente erstellen (TodoList.vue):
vue
<!-- components/TodoList.vue -->
<script setup>
import { ref } from 'vue'
import { useTodoStore } from '../stores/todo'
const todoStore = useTodoStore()
const newTodo = ref('')
const addTodo = () => {
if (newTodo.value.trim() !== '') {
todoStore.addTodo(newTodo.value)
newTodo.value = ''
}
}
</script>
<template>
<div class="todo-list">
<h2>Todo Liste</h2>
<!-- Eingabe -->
<div>
<input
v-model="newTodo"
@keyup.enter="addTodo()"
placeholder="Neue Aufgabe..."
/>
<button @click="addTodo()">Hinzufügen</button>
</div>
<!-- Filter -->
<div>
<button @click="todoStore.setFilter('all')" :class="{ active: todoStore.filter === 'all' }">Alle</button>
<button @click="todoStore.setFilter('done')" :class="{ active: todoStore.filter === 'done' }">Erledigt</button>
<button @click="todoStore.setFilter('undone')" :class="{ active: todoStore.filter === 'undone' }">Offen</button>
</div>
<!-- Liste -->
<ul>
<li v-for="todo in todoStore.filteredTodos" :key="todo.id">
<input
type="checkbox"
:checked="todo.done"
@change="todoStore.toggleTodo(todo.id)"
/>
<span :style="{ textDecoration: todo.done ? 'line-through' : 'none' }">
{{ todo.text }}
</span>
<button @click="todoStore.removeTodo(todo.id)">Löschen</button>
</li>
</ul>
<!-- Statistik -->
<p>Gesamt: {{ todoStore.todos.length }}</p>
<p>Erledigt: {{ todoStore.doneTodos.length }}</p>
<p>Offen: {{ todoStore.undoneTodos.length }}</p>
<button @click="todoStore.reset()">Zurücksetzen</button>
</div>
</template>
<style scoped>
.todo-list {
max-width: 500px;
margin: 0 auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
}
.active {
background: #42b883;
color: white;
}
button {
margin: 0 5px;
padding: 5px 10px;
cursor: pointer;
}
li {
margin: 10px 0;
}
</style>✅ Zusammenfassung
In diesem Kapitel hast du gelernt:
- ✅ State (Zustand) in Pinia
- ✅ Getters (berechnete Eigenschaften)
- ✅ Actions (Aktionen)
- ✅
$reset()(Store zurücksetzen) - ✅
$patch()(State in Batches ändern) - ✅
$subscribe()(State-Änderungen überwachen) - ✅ Praxis: Todo Store mit Pinia (erweitert)
🎯 Nächster Schritt: In Kapitel 35 lernst du Datenpersistenz (LocalStorage)!
← Zurück zu Kapitel 33: Pinia GrundlagenWeiter zu Kapitel 35: Datenpersistenz →
