Skip to content

Kapitel 6: Datenabfrage & Zustandsverwaltung

In diesem Kapitel lernen Sie, wie Sie Daten von APIs abrufen und den Anwendungszustand in Nuxt 3 verwalten.


6.1 Drei Möglichkeiten der Datenabfrage

Nuxt 3 bietet drei Hauptmethoden zum Abrufen von Daten:


Methode 1: useAsyncData() (Empfohlen für komplexe Logik)

useAsyncData() ist die mächtigste Methode zur Datenabfrage.

Einfaches Beispiel:

vue
<script setup>
// Asynchrone Datenabfrage
const { data: posts, pending, error, refresh } = await useAsyncData(
  'posts',  // Eindeutiger Schlüssel (für Caching)
  () => $fetch('/api/posts')  // Asynchrone Funktion
)
</script>

<template>
  <div>
    <!-- Ladezustand -->
    <div v-if="pending">Lädt...</div>

    <!-- Fehlerbehandlung -->
    <div v-else-if="error">Fehler: {{ error.message }}</div>

    <!-- Daten anzeigen -->
    <div v-else>
      <h1>Blogbeiträge</h1>
      <ul>
        <li v-for="post in posts" :key="post.id">
          {{ post.title }}
        </li>
      </ul>
    </div>

    <!-- Manuelles Aktualisieren -->
    <button @click="refresh()">Aktualisieren</button>
  </div>
</template>

Parameter von useAsyncData():

ParameterTypBeschreibung
keystringEindeutiger Schlüssel für Caching
handlerFunctionAsynchrone Funktion zum Abrufen der Daten
optionsObjectZusätzliche Optionen (siehe unten)

Optionen:

javascript
const { data } = await useAsyncData('posts', () => $fetch('/api/posts'), {
  lazy: false,          // Warten auf Daten vor dem Rendern (Standard)
  server: true,        // Auf dem Server ausführen (SSR)
  client: true,        // Im Client ausführen
  immediate: true,     // Sofort ausführen
  watch: []            // Überwachen von reaktiven Werten
})

Methode 2: useFetch() (Vereinfachte Version)

useFetch() ist eine Abkürzung für useAsyncData() + $fetch().

Einfaches Beispiel:

vue
<script setup>
// Einfacher Datenabruf
const { data: posts, pending, error } = await useFetch('/api/posts')
</script>

<template>
  <div>
    <div v-if="pending">Lädt...</div>
    <div v-else-if="error">Fehler!</div>
    <div v-else>
      <pre>{{ posts }}</pre>
    </div>
  </div>
</template>

Mit Optionen:

vue
<script setup>
const { data } = await useFetch('/api/posts', {
  method: 'GET',
  params: {
    page: 1,
    limit: 10
  },
  headers: {
    'Authorization': 'Bearer token'
  }
})
</script>

Methode 3: useLazyFetch() / useLazyAsyncData()

Diese Methoden führen die Datenabfrage nicht blockierend aus. Die Seite wird sofort gerendert, und die Daten werden später geladen.

Beispiel:

vue
<script setup>
// Lazy Loading (nicht blockierend)
const { data: posts, pending } = await useLazyFetch('/api/posts')
</script>

<template>
  <div>
    <!-- Sofort sichtbar -->
    <h1>Blog</h1>

    <!-- Ladeanimation -->
    <div v-if="pending">Lädt Posts...</div>

    <!-- Daten -->
    <div v-else>
      <div v-for="post in posts" :key="post.id">
        {{ post.title }}
      </div>
    </div>
  </div>
</template>

6.2 Datenanfrage kapseln (Request Wrapper)

Für produktionsreife Anwendungen sollten Sie API-Aufrufe zentral verwalten.

Zentraler API-Service erstellen:

utils/api.ts (im utils/-Verzeichnis):

typescript
// Zentraler API-Wrapper
export const useAPI = () => {
  const config = useRuntimeConfig()

  // Basis-URL
  const baseURL = config.public.apiBase || '/api'

  // GET-Anfrage
  const get = async (endpoint: string, options = {}) => {
    return await $fetch(`${baseURL}/${endpoint}`, {
      method: 'GET',
      ...options
    })
  }

  // POST-Anfrage
  const post = async (endpoint: string, body: any, options = {}) => {
    return await $fetch(`${baseURL}/${endpoint}`, {
      method: 'POST',
      body,
      ...options
    })
  }

  // PUT-Anfrage
  const put = async (endpoint: string, body: any, options = {}) => {
    return await $fetch(`${baseURL}/${endpoint}`, {
      method: 'PUT',
      body,
      ...options
    })
  }

  // DELETE-Anfrage
  const del = async (endpoint: string, options = {}) => {
    return await $fetch(`${baseURL}/${endpoint}`, {
      method: 'DELETE',
      ...options
    })
  }

  return {
    get,
    post,
    put,
    delete: del
  }
}

Verwendung im Composable:

composables/usePosts.ts:

typescript
export const usePosts = () => {
  const api = useAPI()

  // Alle Posts abrufen
  const fetchPosts = async () => {
    return await api.get('posts')
  }

  // Einzelnen Post abrufen
  const fetchPost = async (id: string) => {
    return await api.get(`posts/${id}`)
  }

  // Post erstellen
  const createPost = async (postData: any) => {
    return await api.post('posts', postData)
  }

  return {
    fetchPosts,
    fetchPost,
    createPost
  }
}

Verwendung in einer Komponente:

vue
<script setup>
const { fetchPosts } = usePosts()

// Daten abrufen
const { data: posts, pending } = await useAsyncData(
  'posts',
  () => fetchPosts()
)
</script>

<template>
  <div>
    <h1>Blogbeiträge</h1>
    <div v-if="pending">Lädt...</div>
    <ul v-else>
      <li v-for="post in posts" :key="post.id">
        {{ post.title }}
      </li>
    </ul>
  </div>
</template>

6.3 Zustandsverwaltung (State Management)

Nuxt 3 bietet zwei Hauptmethoden zur Zustandsverwaltung:


Methode 1: useState() (Einfach, integriert)

useState() ist eine integrierte Methode zur Zustandsverwaltung. Sie ist SSR-kompatibel und teilt den Zustand zwischen Server und Client.

Globaler Zustand (Composable):

composables/useCounter.ts:

typescript
export const useCounter = () => {
  // Globaler Zustand (überall verfügbar)
  const count = useState('counter', () => 0)

  // Zähler erhöhen
  const increment = () => {
    count.value++
  }

  // Zähler zurücksetzen
  const reset = () => {
    count.value = 0
  }

  return {
    count: readonly(count),  // Schreibgeschützt exportieren
    increment,
    reset
  }
}

Verwendung in Komponenten:

vue
<script setup>
const { count, increment, reset } = useCounter()
</script>

<template>
  <div>
    <p>Zähler: {{ count }}</p>
    <button @click="increment">+1</button>
    <button @click="reset">Zurücksetzen</button>
  </div>
</template>

Methode 2: Pinia (Für komplexe Anwendungen)

Für komplexe Zustandslogik empfiehlt sich Pinia (offizielle Vue 3 State-Management-Bibliothek).

Pinia installieren:

bash
pnpm add @pinia/nuxt

In nuxt.config.ts konfigurieren:

typescript
export default defineNuxtConfig({
  modules: [
    '@pinia/nuxt'
  ]
})

Pinia Store erstellen:

stores/counter.ts:

typescript
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
    todos: []
  }),

  getters: {
    doubled: (state) => state.count * 2
  },

  actions: {
    increment() {
      this.count++
    },
    addTodo(todo: string) {
      this.todos.push(todo)
    }
  }
})

Verwendung in Komponenten:

vue
<script setup>
import { useCounterStore } from '~/stores/counter'

const store = useCounterStore()
</script>

<template>
  <div>
    <p>Zähler: {{ store.count }}</p>
    <p>Verdoppelt: {{ store.doubled }}</p>
    <button @click="store.increment()">+1</button>
  </div>
</template>

6.4 Lokaler Speicher (Local Storage & Cookies)

useCookie() (Cookies verwalten)

Nuxt 3 bietet das Composable useCookie() zur Cookie-Verwaltung.

Beispiel:

vue
<script setup>
// Cookie setzen (läuft auf Server & Client)
const userToken = useCookie('token', {
  maxAge: 60 * 60 * 24,  // 1 Tag
  secure: true,
  sameSite: 'strict'
})

// Cookie setzen
const setToken = () => {
  userToken.value = 'abc123'
}

// Cookie lesen
console.log(userToken.value)

// Cookie löschen
const removeToken = () => {
  userToken.value = null
}
</script>

<template>
  <div>
    <button @click="setToken">Token setzen</button>
    <button @click="removeToken">Token löschen</button>
  </div>
</template>

LocalStorage (Nur Client-Seite!)

Achtung: localStorage ist nicht auf dem Server verfügbar (SSR).

Sichere Verwendung:

vue
<script setup>
const userPreferences = ref(null)

// Nur im Client ausführen!
onMounted(() => {
  const stored = localStorage.getItem('preferences')
  if (stored) {
    userPreferences.value = JSON.parse(stored)
  }
})

// Speichern
const savePreferences = () => {
  localStorage.setItem('preferences', JSON.stringify(userPreferences.value))
}
</script>

6.5 Zusammenfassung

In diesem Kapitel haben Sie gelernt:

  • ✅ Drei Methoden zur Datenabfrage (useAsyncData, useFetch, useLazyFetch)
  • ✅ API-Anfragen zentral zu kapseln (Request Wrapper)
  • ✅ Zustandsverwaltung mit useState() (einfach) und Pinia (komplex)
  • ✅ Cookies mit useCookie() zu verwalten
  • ✅ LocalStorage sicher zu verwenden (nur Client-Seite)

Nächste Schritte: Im nächsten Kapitel lernen wir Layouts & Komponentenentwicklung – wie man wiederverwendbare Komponenten erstellt und Layouts verwendet.


📚 Weiterführende Ressourcen


Nächstes Kapitel: Kapitel 7: Layouts & Komponentenentwicklung →

Frei für alle Anfänger