Skip to content

Kapitel 25: Composition API (Teil 5) - Benutzerdefinierte Composables

📙 Lernziel: Eigene Composables erstellen - Logik wiederverwenden!


25.1 Was sind benutzerdefinierte Composables?

Composables sind JavaScript-Funktionen, die Composition API nutzen, um Logik wiederzuverwenden.

Vorteile:

  • ✅ Logik-Wiederverwendung (DRY - Don't Repeat Yourself)
  • ✅ Bessere Organisation (verwandte Logik zusammen)
  • ✅ Testbarkeit
  • ✅ TypeScript-Unterstützung

Beispiel-Szenario:

useCounter.js       → Zähler-Logik
useFetch.js         → API-Aufrufe
useLocalStorage.js  → Lokaler Speicher
useWindowSize.js   → Fenstergröße überwachen

25.2 Einfaches Composable erstellen: useCounter.js

Schritt 1: Composable-Datei erstellen

javascript
// composables/useCounter.js
import { ref } from 'vue'

export function useCounter(initialValue = 0, step = 1) {
  const count = ref(initialValue)
  
  const increment = () => {
    count.value += step
  }
  
  const decrement = () => {
    count.value -= step
  }
  
  const reset = () => {
    count.value = initialValue
  }
  
  return {
    count,
    increment,
    decrement,
    reset
  }
}

Schritt 2: In Komponente verwenden

vue
<script setup>
import { useCounter } from '../composables/useCounter.js'

// Logik wiederverwenden
const { count, increment, decrement, reset } = useCounter(0, 1)
</script>

<template>
  <div>
    <h2>Zähler: {{ count }}</h2>
    <button @click="decrement">-</button>
    <span>{{ count }}</span>
    <button @click="increment">+</button>
    <button @click="reset">Zurücksetzen</button>
  </div>
</template>

Vorteile:

  • ✅ Logik ist gekapselt
  • ✅ In mehreren Komponenten wiederverwendbar
  • ✅ Einfach zu testen

25.3 Composable mit Parameter: useFetch.js

Beispiel: Composable für API-Aufrufe

javascript
// composables/useFetch.js
import { ref } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const error = ref(null)
  const isLoading = ref(false)
  
  const fetchData = async () => {
    isLoading.value = true
    error.value = null
    
    try {
      const response = await fetch(url)
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`)
      }
      data.value = await response.json()
    } catch (err) {
      error.value = err.message
    } finally {
      isLoading.value = false
    }
  }
  
  // Sofort ausführen
  fetchData()
  
  return {
    data,
    error,
    isLoading,
    fetchData  // Manuell neu laden
  }
}

Verwendung:

vue
<script setup>
import { useFetch } from '../composables/useFetch.js'

// API-Aufruf
const { data: posts, error, isLoading, fetchData } = useFetch('https://jsonplaceholder.typicode.com/posts')
</script>

<template>
  <div>
    <h2>Posts</h2>
    <div v-if="isLoading">Lädt...</div>
    <div v-else-if="error">Fehler: {{ error }}</div>
    <ul v-else>
      <li v-for="post in posts.slice(0, 5)" :key="post.id">
        {{ post.title }}
      </li>
    </ul>
    <button @click="fetchData">Neu laden</button>
  </div>
</template>

25.4 Composable mit anderen Composables kombinieren

Beispiel: useLocalStorage.js mit useCounter.js kombinieren

javascript
// composables/useLocalStorage.js
import { ref, watch } from 'vue'

export function useLocalStorage(key, defaultValue) {
  const storedValue = localStorage.getItem(key)
  const data = ref(storedValue ? JSON.parse(storedValue) : defaultValue)
  
  // Automatisch in localStorage speichern wenn sich Daten ändern
  watch(data, (newValue) => {
    localStorage.setItem(key, JSON.stringify(newValue))
  }, { deep: true })
  
  const remove = () => {
    localStorage.removeItem(key)
    data.value = defaultValue
  }
  
  return {
    data,
    remove
  }
}

Kombinieren:

vue
<script setup>
import { useCounter } from '../composables/useCounter.js'
import { useLocalStorage } from '../composables/useLocalStorage.js'

// Zähler mit localStorage Persistenz
const { data: savedCount, remove } = useLocalStorage('counter', 0)
const { count, increment, decrement, reset } = useCounter(savedCount.value)

// Zählerstand speichern
watch(count, (newValue) => {
  savedCount.value = newValue
})
</script>

<template>
  <div>
    <h2>Zähler: {{ count }}</h2>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
    <button @click="reset(); remove()">Zurücksetzen & Löschen</button>
    <p>(Wert wird im localStorage gespeichert!)</p>
  </div>
</template>

25.5 Best Practices für Composables

1. Benennungskonvention:

  • ✅ Immer mit use-Präfix (z.B. useCounter, useFetch)
  • ✅ In composables/ Ordner speichern

2. Rückgabewerte:

  • ✅ Objekt mit benannten Eigenschaften zurückgeben
  • ✅ Destrukturierung ermöglichen

3. Reaktivität beibehalten:

  • ref() und reactive() verwenden
  • toRefs() nutzen, um Reaktivität bei Destrukturierung zu erhalten

4. Fehlerbehandlung:

  • ✅ Try-catch Blöcke verwenden
  • ✅ Fehler als ref() zurückgeben

5. Dokumentation:

  • ✅ JSDoc-Kommentare hinzufügen
  • ✅ Parameter und Rückgabewerte erklären

Beispiel (gut dokumentiert):

javascript
/**
 * Composable für Zähler-Funktionalität
 * @param {number} initialValue - Startwert (Standard: 0)
 * @param {number} step - Schrittweite (Standard: 1)
 * @returns {Object} - { count, increment, decrement, reset }
 */
export function useCounter(initialValue = 0, step = 1) {
  const count = ref(initialValue)
  
  const increment = () => {
    count.value += step
  }
  
  const decrement = () => {
    count.value -= step
  }
  
  const reset = () => {
    count.value = initialValue
  }
  
  return {
    count,
    increment,
    decrement,
    reset
  }
}

25.6 Übung: useWindowSize.js Composable

Aufgabe: Erstelle ein Composable, das Fenstergröße überwacht.

Lösung:

javascript
// composables/useWindowSize.js
import { ref, onMounted, onUnmounted } from 'vue'

export function useWindowSize() {
  const width = ref(window.innerWidth)
  const height = ref(window.innerHeight)
  
  const updateSize = () => {
    width.value = window.innerWidth
    height.value = window.innerHeight
  }
  
  // Event-Listener hinzufügen
  onMounted(() => {
    window.addEventListener('resize', updateSize)
  })
  
  // Event-Listener entfernen (Cleanup)
  onUnmounted(() => {
    window.removeEventListener('resize', updateSize)
  })
  
  return {
    width,
    height
  }
}

Verwendung:

vue
<script setup>
import { useWindowSize } from '../composables/useWindowSize.js'

const { width, height } = useWindowSize()
</script>

<template>
  <div>
    <h2>Fenstergröße</h2>
    <p>Breite: {{ width }}px</p>
    <p>Höhe: {{ height }}px</p>
    <div 
      :style="{ 
        width: '100%', 
        height: '50px', 
        backgroundColor: width > 768 ? 'lightgreen' : 'lightcoral' 
      }"
    >
      {{ width > 768 ? 'Desktop' : 'Mobile' }}
    </div>
  </div>
</template>

✅ Zusammenfassung

In diesem Kapitel hast du gelernt:

  • ✅ Was benutzerdefinierte Composables sind
  • ✅ Einfaches Composable erstellen (useCounter.js)
  • ✅ Composable mit Parameter (useFetch.js)
  • ✅ Composables kombinieren
  • ✅ Best Practices für Composables
  • ✅ Praxis: useWindowSize.js Composable

🎯 Nächster Schritt: In Kapitel 26 lernst du Template Rendering & Event-Handling!


← Zurück zu Kapitel 24: Lebenszyklus-HooksWeiter zu Kapitel 26: Template & Events →

Frei für alle Anfänger