Skip to content

Kapitel 35: Pinia (Teil 3) - Datenpersistenz

📙 Lernziel: Pinia State im LocalStorage speichern (Persistenz)!


35.1 Was ist Datenpersistenz?

Datenpersistenz bedeutet, Daten über Browser-Sitzungen hinweg zu speichern.

Anwendungsfälle:

  • ✅ Einkaufswagen (Cart)
  • ✅ Benutzereinstellungen (Theme, Sprache)
  • ✅ Formulardaten (Draft)
  • ✅ Login-Status (Token)

Beispiel-Szenario:

Benutzer ändert Theme → Speichern in LocalStorage
Browser neu laden → Theme aus LocalStorage laden

35.2 LocalStorage Grundlagen

LocalStorage API:

javascript
// Speichern
localStorage.setItem('key', 'value')

// Laden
const value = localStorage.getItem('key')

// Entfernen
localStorage.removeItem('key')

// Alle entfernen
localStorage.clear()

Wichtig:

  • ✅ Speichert nur Strings (JSON.stringify() nutzen!)
  • ✅ 5MB Speicherplatz (pro Origin)
  • ✅ Bleibt auch nach Browser-Neustart erhalten

Beispiel:

javascript
// Objekt speichern
const user = { name: 'Max', age: 25 }
localStorage.setItem('user', JSON.stringify(user))

// Objekt laden
const storedUser = JSON.parse(localStorage.getItem('user'))
console.log(storedUser.name) // 'Max'

35.3 Pinia Plugin für Persistenz

Plugin erstellen (plugins/persistedState.js):

javascript
// plugins/persistedState.js
export function persistedState(store) {
  // 1. Beim Erstellen: State aus LocalStorage laden
  const storedState = localStorage.getItem(store.$id)
  if (storedState) {
    store.$patch(JSON.parse(storedState))
  }

  // 2. Bei jeder State-Änderung: In LocalStorage speichern
  store.$subscribe((mutation, state) => {
    localStorage.setItem(store.$id, JSON.stringify(state))
  })
}

Verwendung im Store:

javascript
// stores/counter.js
import { defineStore } from 'pinia'
import { persistedState } from '../plugins/persistedState'

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)

  const increment = () => {
    count.value++
  }

  return {
    count,
    increment
  }
})

// Plugin registrieren
useCounterStore().$onAction(persistedState)

Einfacher (Plugin global registrieren):

javascript
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { persistedState } from './plugins/persistedState'
import App from './App.vue'

const pinia = createPinia()

// Plugin für alle Stores registrieren
pinia.use(persistedState)

createApp(App)
  .use(pinia)
  .mount('#app')

35.4 Manuelle Persistenz (Ohne Plugin)

Beispiel: Todo Store mit manueller Persistenz

javascript
// stores/todo.js
import { defineStore } from 'pinia'
import { ref, watch } from 'vue'

export const useTodoStore = defineStore('todo', () => {
  // 1. State aus LocalStorage laden (beim Erstellen)
  const storedTodos = localStorage.getItem('todos')
  const todos = ref(storedTodos ? JSON.parse(storedTodos) : [
    { id: 1, text: 'Lernen', done: false },
    { id: 2, text: 'Sport', done: true }
  ])

  // 2. Watch: Bei jeder Änderung in LocalStorage speichern
  watch(todos, (newTodos) => {
    localStorage.setItem('todos', JSON.stringify(newTodos))
  }, { deep: true })

  // 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
    }
  }

  return {
    todos,
    addTodo,
    removeTodo,
    toggleTodo
  }
})

Verwendung:

vue
<script setup>
import { useTodoStore } from '../stores/todo'

const todoStore = useTodoStore()
const newTodo = ref('')
</script>

<template>
  <div>
    <h2>Todo Liste</h2>
    
    <input 
      v-model="newTodo" 
      @keyup.enter="todoStore.addTodo(newTodo); newTodo = ''"
      placeholder="Neue Aufgabe..."
    />
    <button @click="todoStore.addTodo(newTodo); newTodo = ''">Hinzufügen</button>
    
    <ul>
      <li v-for="todo in todoStore.todos" :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>
    
    <button @click="localStorage.removeItem('todos')">Persistenz löschen</button>
  </div>
</template>

Vorteile manuelle Persistenz:

  • ✅ Volle Kontrolle
  • ✅ Selektive Persistenz (nur bestimmte Felder)
  • ✅ Einfacher zu debuggen

35.5 pinia-plugin-persistedstate (Bibliothek)

Installation:

bash
pnpm add pinia-plugin-persistedstate

Verwendung:

javascript
// stores/counter.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)

  const doubleCount = computed(() => count.value * 2)

  const increment = () => {
    count.value++
  }

  return {
    count,
    doubleCount,
    increment
  }
}, {
  // Persistenz aktivieren
  persist: true
})

Konfiguration:

javascript
// stores/counter.js
export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const user = ref({ name: '', age: 0 })

  return {
    count,
    user
  }
}, {
  persist: {
    key: 'my-counter', // Custom key
    storage: localStorage, // oder sessionStorage
    paths: ['count'] // Nur 'count' persistieren (nicht 'user')
  }
})

Plugin global registrieren:

javascript
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { persistedState } from 'pinia-plugin-persistedstate'
import App from './App.vue'

const pinia = createPinia()

// Plugin registrieren
pinia.use(persistedState)

createApp(App)
  .use(pinia)
  .mount('#app')

35.6 Übung: Theme Store mit Persistenz

Aufgabe: Erstelle einen Theme Store, der das Theme im LocalStorage speichert.

Lösung:

1. Store erstellen (stores/theme.js):

javascript
// stores/theme.js
import { defineStore } from 'pinia'
import { ref, watch } from 'vue'

export const useThemeStore = defineStore('theme', () => {
  // Aus LocalStorage laden (oder Standard: 'light')
  const storedTheme = localStorage.getItem('theme')
  const theme = ref(storedTheme || 'light')

  // Watch: Bei Änderung in LocalStorage speichern
  watch(theme, (newTheme) => {
    localStorage.setItem('theme', newTheme)
    // Theme auf <html> anwenden
    document.documentElement.setAttribute('data-theme', newTheme)
  })

  // Beim Erstellen: Theme auf <html> anwenden
  if (theme.value) {
    document.documentElement.setAttribute('data-theme', theme.value)
  }

  const toggleTheme = () => {
    theme.value = theme.value === 'light' ? 'dark' : 'light'
  }

  return {
    theme,
    toggleTheme
  }
})

2. Komponente erstellen (ThemeSwitcher.vue):

vue
<!-- components/ThemeSwitcher.vue -->
<script setup>
import { useThemeStore } from '../stores/theme'

const themeStore = useThemeStore()
</script>

<template>
  <div class="theme-switcher">
    <p>Aktuelles Theme: {{ themeStore.theme }}</p>
    <button @click="themeStore.toggleTheme()">
      Wechseln zu {{ themeStore.theme === 'light' ? 'Dark' : 'Light' }} Mode
    </button>
  </div>
</template>

<style scoped>
.theme-switcher {
  position: fixed;
  top: 20px;
  right: 20px;
  padding: 10px 20px;
  background: var(--bg-color, white);
  color: var(--text-color, black);
  border: 1px solid #ddd;
  border-radius: 8px;
}

button {
  padding: 5px 10px;
  cursor: pointer;
}
</style>

3. CSS Variablen definieren (style.css):

css
/* style.css */
[data-theme="light"] {
  --bg-color: #ffffff;
  --text-color: #000000;
}

[data-theme="dark"] {
  --bg-color: #1a1a1a;
  --text-color: #ffffff;
}

body {
  background: var(--bg-color);
  color: var(--text-color);
  transition: background 0.3s, color 0.3s;
}

4. In App.vue verwenden:

vue
<!-- App.vue -->
<script setup>
import ThemeSwitcher from './components/ThemeSwitcher.vue'
</script>

<template>
  <div>
    <ThemeSwitcher />
    <h1>Meine App</h1>
    <p>Willkommen!</p>
  </div>
</template>

Ergebnis:

  • ✅ Theme wird im LocalStorage gespeichert
  • ✅ Beim Seitenaufruf wird Theme automatisch geladen
  • ✅ Sofortige visuelle Änderung

✅ Zusammenfassung

In diesem Kapitel hast du gelernt:

  • ✅ Was Datenpersistenz ist
  • ✅ LocalStorage Grundlagen
  • ✅ Pinia Plugin für Persistenz
  • ✅ Manuelle Persistenz (ohne Plugin)
  • pinia-plugin-persistedstate Bibliothek
  • ✅ Praxis: Theme Store mit Persistenz

🎯 Nächster Schritt: In Kapitel 36 lernst du axios (Netzwerkanfragen)!


← Zurück zu Kapitel 34: State, Getters & ActionsWeiter zu Kapitel 36: axios Grundlagen →

Frei für alle Anfänger