Composables schreiben
Eigene Composables erstellen – Vues mächtigstes Werkzeug für wiederverwendbare Logik.
Was ist ein Composable?
Ein Composable ist eine Funktion, die Vues Composition API nutzt, um reaktiven State und Logik zu kapseln. Der Name kommt von „compose" – du komponierst deine Komponenten-Logik aus kleinen, wiederverwendbaren Bausteinen.
Technisch gesehen ist ein Composable einfach eine Funktion, die:
- Reaktiven State erstellt (
ref,reactive,computed) - Lifecycle-Hooks nutzen kann (
onMounted,onUnmounted) - State und Methoden als Objekt zurückgibt
- In jeder Komponente per Import genutzt werden kann
Rails-Vergleich
Composables sind wie Service Objects oder POROs in Rails – eigenständige Klassen/Module, die eine bestimmte Aufgabe kapseln. Der Unterschied: Composables sind automatisch reaktiv. Stell dir ein Service Object vor, das sich selbst aktualisiert, wenn sich seine Eingabedaten ändern.
Namenskonvention: useXxx
Per Konvention beginnen Composables immer mit use – z.B. useCounter, useFetch, useAuth. Das hat mehrere Vorteile:
- Sofort als Composable erkennbar
- IDE-Autocomplete: Tippe
useund sieh alle verfügbaren Composables - In Nuxt werden
composables/-Dateien mituse-Prefix automatisch importiert - Konsistent mit React Hooks und der VueUse-Bibliothek
composables/
├── useAuth.ts # Authentifizierung
├── useCounter.ts # Zähler-Logik
├── useDarkMode.ts # Theme-Umschaltung
├── useFetch.ts # API-Aufrufe
└── useMousePosition.ts # Maus-TrackingNuxt Auto-Import
In Nuxt 3 werden alle Dateien im composables/-Verzeichnis automatisch importiert. Du brauchst kein import-Statement – schreib einfach useCounter() in deiner Komponente.
Dein erstes Composable: useCounter
Beginnen wir mit einem einfachen Beispiel. Dieses Composable kapselt einen Zähler mit Increment, Decrement und Reset:
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
function increment() {
count.value++
}
function decrement() {
count.value--
}
function reset() {
count.value = initialValue
}
return {
count: readonly(count), // Nur lesen von außen
increment,
decrement,
reset
}
} Beachte die Struktur: Die Funktion erstellt reaktiven State, definiert Methoden, und gibt alles als Objekt zurück. Jeder Aufruf von useCounter() erstellt eine eigene Instanz mit eigenem State.
Composables in Komponenten nutzen
Die Nutzung ist denkbar einfach – Funktion aufrufen und die Rückgabewerte destrukturieren:
<template>
<div>
<p>Zähler: {{ count }}</p>
<button @click="increment">+</button>
<button @click="decrement">−</button>
<button @click="reset">Reset</button>
</div>
</template>
<script setup>
// In Nuxt: Kein Import nötig (auto-imported aus composables/)
const { count, increment, decrement, reset } = useCounter(10)
</script>Destrukturierung und Reaktivität
Da count ein ref ist, bleibt die Reaktivität beim Destrukturieren erhalten. Bei reactive-Objekten wäre das nicht der Fall – deshalb geben die meisten Composables ref-Werte zurück.
Reaktiven State zurückgeben
Es gibt verschiedene Patterns, wie ein Composable seinen State zurückgeben kann:
// Option 1: Einzelne Refs zurückgeben (empfohlen)
function useCounter() {
const count = ref(0)
const doubled = computed(() => count.value * 2)
return { count, doubled }
}
// Nutzung: const { count, doubled } = useCounter()
// Option 2: Reactive-Objekt zurückgeben
function useCounter() {
const state = reactive({ count: 0 })
return toRefs(state) // In Refs konvertieren!
}
// Option 3: Readonly für geschützten State
function useCounter() {
const count = ref(0)
return {
count: readonly(count), // Von außen nicht änderbar
increment: () => count.value++
}
}Empfehlung
Nutze Option 1 (einzelne Refs) als Standard. Das ist am flexibelsten – Konsumenten können genau das importieren, was sie brauchen, und Refs behalten ihre Reaktivität beim Destrukturieren.
Praxis: useMousePosition
Ein realistischeres Beispiel: Ein Composable, das die Mausposition verfolgt. Hier siehst du auch Lifecycle-Hooks in einem Composable – die Event-Listener werden beim Mounten registriert und beim Unmounten entfernt:
import { ref, onMounted, onUnmounted } from 'vue'
export function useMousePosition() {
const x = ref(0)
const y = ref(0)
function update(event: MouseEvent) {
x.value = event.pageX
y.value = event.pageY
}
onMounted(() => {
window.addEventListener('mousemove', update)
})
onUnmounted(() => {
window.removeEventListener('mousemove', update)
})
return { x, y }
}
// Nutzung in einer Komponente:
// const { x, y } = useMousePosition()Cleanup ist entscheidend
Der onUnmounted-Hook entfernt den Event-Listener. Ohne Cleanup würde bei jedem Mounten/Unmounten ein neuer Listener dazukommen – ein klassisches Memory Leak. In Rails passiert das Äquivalent bei WebSocket-Subscriptions in Action Cable, die nicht sauber abgemeldet werden.
Composables mit Parametern
Composables können Parameter entgegennehmen, um ihr Verhalten zu konfigurieren. Akzeptiere sowohl einfache Werte als auch Refs – das macht das Composable flexibler:
import { ref, watch, type MaybeRef } from 'vue'
export function useTitle(newTitle: MaybeRef<string>) {
const title = ref(toValue(newTitle))
// Wenn ein Ref übergeben wird, reagiere auf Änderungen
watch(
() => toValue(newTitle),
(val) => {
title.value = val
if (false) {
document.title = val
}
},
{ immediate: true }
)
return title
}
// Nutzung:
// useTitle('Meine Seite') // Statisch
// useTitle(computed(() => user.value.name)) // ReaktivMaybeRef Pattern
Der Typ MaybeRef<T> akzeptiert sowohl T als auch Ref<T>. Mit toValue() bekommst du immer den einfachen Wert heraus. So kann der Konsument entscheiden, ob der Parameter reaktiv sein soll oder nicht.
Best Practices
Immer mit use beginnen
useAuth, useDarkMode, useApi – konsistent und sofort erkennbar.
Refs statt Reactive zurückgeben
Refs behalten Reaktivität beim Destrukturieren – reactive nicht.
Cleanup in onUnmounted
Listener, Timer und Subscriptions immer aufräumen.
Composables nicht in Schleifen oder Conditions aufrufen
Immer auf Top-Level von setup aufrufen – wie React Hooks.
Kein globaler State ohne Absicht
State innerhalb der Funktion = pro Instanz. State außerhalb = geteilt (singleton).
Zusammenfassung
Composables sind Vues Antwort auf die Frage: „Wie teile ich Logik zwischen Komponenten?" Sie sind einfache Funktionen, die reaktiven State kapseln und zurückgeben. Mit der use-Konvention, sauberem Cleanup und Ref-basierten Rückgabewerten baust du eine Bibliothek wiederverwendbarer Bausteine, die dein Projekt skalierbar halten. Im nächsten Kapitel schauen wir uns fortgeschrittene Composable-Patterns an.