die Brücke zwischen reactive() und ref()
Eine normale Destrukturierung zerstört die Reaktivität eines reactive()-Objekts, weil einzelne Felder zu einfachen Kopien werden. toRef() und toRefs() lösen genau dieses Problem, indem sie jede Property in einen eigenständigen Ref verwandeln, der weiterhin an die Originalquelle gekoppelt bleibt.
Inhaltsverzeichnis
- 1. Warum reactive() und ref() aneinander vorbeireden können
- 2. toRef() im Detail: wie die Kopplung funktioniert
- 3. toRefs(): ein ganzes Objekt in Refs zerlegen
- 4. Composable-Rückgaben ohne Reaktivitätsverlust destrukturieren
- 5. toRef() mit einem Getter statt einer Property
- 6. Ref-Unwrapping in Templates vs. in Script
- 7. Historischer Fehler: toRef() auf fehlende Properties
- 8. toRef()/toRefs() als feste API-Konvention in Composables
- 9. toRef() vs. toRefs() vs. direkte Destrukturierung
- 10. Zusammenfassung
- 11. FAQ
1. Warum reactive() und ref() aneinander vorbeireden können
Ein mit reactive() erzeugtes Objekt ist reaktiv, solange man über die Objektreferenz selbst darauf zugreift. Sobald eine einzelne Property destrukturiert wird, etwa mit const { count } = state, entsteht eine einfache, nicht reaktive Kopie des aktuellen Werts. Änderungen an state.count danach haben keinerlei Effekt mehr auf die lokale Variable count, ein Verhalten, das viele Einsteiger überrascht, weil es der Destrukturierung von normalen JavaScript-Objekten entspricht, aber nicht der Erwartung an ein reaktives System.
Genau hier setzen toRef() und toRefs() an. Beide Funktionen erzeugen aus einer Property eines reactive()-Objekts einen echten ref(), der weiterhin an die Originalquelle gekoppelt bleibt: Lesen des Refs liest den aktuellen Wert der Quelle, Schreiben des Refs schreibt zurück in die Quelle. Diese Kopplung erlaubt, einzelne Felder aus einem reaktiven Objekt herauszulösen, ohne die Verbindung zur ursprünglichen Datenquelle zu kappen.
Der praktische Nutzen zeigt sich vor allem in Composables. Eine Funktion, die intern ein reactive()-Objekt mit mehreren Feldern verwaltet, kann mit toRefs() jedes einzelne Feld als eigenständigen Ref nach außen geben, sodass Konsumenten einzelne Werte destrukturieren können, ohne die Reaktivität zu verlieren, ein Muster, das in nahezu jeder öffentlichen Composable-API von VueUse und ähnlichen Bibliotheken zu finden ist.
2. toRef() im Detail: wie die Kopplung funktioniert
toRef(source, key) erzeugt einen Ref, dessen .value-Zugriff intern auf source[key] umgeleitet wird. Anders als bei einer Kopie mit destrukturierender Zuweisung entsteht hier kein neuer, unabhängiger Speicherplatz für den Wert. Stattdessen fungiert der zurückgegebene Ref als reiner Proxy auf die ursprüngliche Property: Ein Lesezugriff auf .value liest live von source[key], ein Schreibzugriff schreibt live zurück.
Diese Kopplung funktioniert in beide Richtungen. Ändert eine andere Komponente oder Funktion source.key direkt, spiegelt sich das sofort im Wert des über toRef() erzeugten Refs wider, weil beide letztlich auf dieselbe zugrunde liegende reaktive Property zugreifen. Umgekehrt aktualisiert eine Zuweisung an myRef.value auch source.key, was toRef() von einer einfachen einmaligen Kopie klar unterscheidet.
import { reactive, toRef } from 'vue'
const state = reactive({ count: 0, name: 'Vue' })
// countRef stays coupled to state.count — it is NOT an independent copy
const countRef = toRef(state, 'count')
console.log(countRef.value) // 0
state.count = 5
console.log(countRef.value) // 5 — reflects the change to the source object
countRef.value = 10
console.log(state.count) // 10 — writing back through the ref updates the source
3. toRefs(): ein ganzes Objekt in Refs zerlegen
toRefs(source) ist die praktische Erweiterung von toRef() für den Fall, dass nicht nur eine, sondern alle Properties eines reaktiven Objekts als eigenständige, gekoppelte Refs benötigt werden. Statt für jedes Feld einzeln toRef(state, 'feld') aufzurufen, erzeugt toRefs() in einem Aufruf ein neues Objekt, dessen Werte allesamt Refs sind, jeweils gekoppelt an die entsprechende Property der Quelle.
Der entscheidende Vorteil zeigt sich bei der Destrukturierung: Wird das Ergebnis von toRefs(state) destrukturiert, bleibt jede destrukturierte Variable reaktiv, weil sie bereits ein eigenständiger Ref ist, kein einfacher Wert. Genau das macht toRefs() zum Standardwerkzeug am Ende fast jeder Composable-Funktion, die intern mit reactive() arbeitet, aber nach außen destrukturierbare, individuell reaktive Werte anbieten möchte.
import { reactive, toRefs } from 'vue'
const state = reactive({
count: 0,
name: 'Vue',
isActive: true
})
// All three properties become individually coupled refs at once
const { count, name, isActive } = toRefs(state)
count.value++ // updates state.count too
state.name = 'Vue 3' // name.value reflects this change automatically
console.log(count.value, name.value, isActive.value) // 1 'Vue 3' true
4. Composable-Rückgaben ohne Reaktivitätsverlust destrukturieren
Der häufigste Praxisfall für toRefs() ist die letzte Zeile eines Composables. Wird intern ein reactive()-Objekt für den gesamten internen Zustand verwendet, weil das die Implementierung übersichtlicher macht, muss beim Zurückgeben trotzdem sichergestellt werden, dass Konsumenten einzelne Felder destrukturieren können, ohne die Reaktivität zu verlieren. Ohne toRefs() am Ende würde const { x, y } = useMousePosition() zwei eingefrorene Werte liefern, die sich nie wieder ändern.
Diese Konvention ist inzwischen so etabliert, dass sie fast als Vertrag zwischen Composable-Autor und Konsument gilt: Ein Composable, das ein Objekt mit mehreren reaktiven Werten zurückgibt, sollte dieses Objekt am Rückgabepunkt durch toRefs() schicken, damit die Rückgabe sowohl als Ganzes verwendet als auch sicher destrukturiert werden kann, je nachdem was der Konsument bevorzugt.
import { reactive, toRefs, onMounted, onBeforeUnmount } from 'vue'
export function useMousePosition() {
// Internally, a single reactive object is easier to reason about
const state = reactive({ x: 0, y: 0 })
function update(event) {
state.x = event.clientX
state.y = event.clientY
}
onMounted(() => window.addEventListener('mousemove', update))
onBeforeUnmount(() => window.removeEventListener('mousemove', update))
// toRefs() at the return point keeps x and y independently reactive
return toRefs(state)
}
// In a component:
// const { x, y } = useMousePosition()
// x and y stay reactive, even though they were destructured
5. toRef() mit einem Getter statt einer Property
Seit Vue 3.3 akzeptiert toRef() auch eine Getter-Funktion als einzigen Parameter, statt einer Quelle plus Property-Namen. Das erzeugte Ergebnis ist ein read-only Ref, dessen Wert bei jedem Zugriff neu über die Getter-Funktion berechnet wird, ähnlich wie computed(), aber ohne dessen internes Caching. Das ist nützlich, wenn eine einfache Transformation eines bestehenden Werts als Ref weitergegeben werden soll, ohne extra einen vollständigen computed() anzulegen.
Der Unterschied zu computed() ist subtil, aber relevant: Ein Getter-basierter toRef() wird bei jedem Lesezugriff neu ausgewertet, während computed() das Ergebnis zwischenspeichert und nur bei tatsächlicher Änderung der Abhängigkeiten neu berechnet. Für teure Berechnungen bleibt computed() daher die bessere Wahl, für einfache, günstige Transformationen ist der Getter-toRef() eine kompaktere Alternative.
import { ref, toRef } from 'vue'
const celsius = ref(20)
// toRef() with a getter function (Vue 3.3+): read-only, re-evaluated on every read
const fahrenheit = toRef(() => celsius.value * 9 / 5 + 32)
console.log(fahrenheit.value) // 68
celsius.value = 25
console.log(fahrenheit.value) // 77 — re-evaluated from the getter, no caching
6. Ref-Unwrapping in Templates vs. in Script
Ein häufiger Verwirrungspunkt bei toRef() und toRefs() ist das automatische Ref-Unwrapping in Templates. Im <template>-Block greift man auf einen mit toRefs() erzeugten Ref direkt ohne .value zu, weil Vue Top-Level-Refs im Template automatisch auflöst. Im <script setup>-Block dagegen ist .value zwingend erforderlich, da dort kein automatisches Unwrapping stattfindet, außer bei Refs, die bereits innerhalb eines reactive()-Objekts liegen.
Diese Asymmetrie führt gelegentlich zu Verwirrung, wenn Entwickler einen mit toRefs() destrukturierten Wert im Script-Teil ohne .value verwenden, weil sie das automatische Unwrapping aus dem Template gewohnt sind. Die Faustregel bleibt: Im Template niemals .value schreiben, im Script-Teil außerhalb von Templates immer .value verwenden, wenn es sich um einen echten Ref handelt, unabhängig davon ob er über ref(), toRef() oder toRefs() entstanden ist.
7. Historischer Fehler: toRef() auf fehlende Properties
Vor Vue 3.3 gab es eine Einschränkung, die häufig zu Verwirrung führte: toRef(source, key) für eine Property, die zum Zeitpunkt des Aufrufs noch nicht auf source existierte, lieferte einen Ref, der niemals reaktiv wurde, selbst wenn die Property später hinzugefügt wurde. Der Grund war, dass Vue zur Erzeugung des Refs prüfen musste, ob die Property überhaupt existiert, und bei einem negativen Ergebnis einen einfachen, nicht reaktiv gekoppelten Fallback-Ref zurückgab.
Seit Vue 3.3 wurde dieses Verhalten korrigiert: toRef() funktioniert nun auch für Properties, die erst später zum Quellobjekt hinzugefügt werden, solange die Quelle selbst reaktiv ist. Wer Code aus älteren Vue-3-Versionen migriert, sollte auf genau dieses historische Detail achten, insbesondere bei Composables, die optionale Felder erst bedingt zu einem reactive()-Objekt hinzufügen.
8. toRef()/toRefs() als feste API-Konvention in Composables
Composable-Bibliotheken wie VueUse etablieren toRefs() als festen Bestandteil ihrer öffentlichen API-Konvention: Fast jede Funktion, die mehrere reaktive Werte zurückgibt, tut dies über ein mit toRefs() aufbereitetes Objekt, damit Konsumenten frei wählen können, ob sie das gesamte Ergebnisobjekt behalten oder gezielt einzelne Felder destrukturieren. Diese Konvention zu übernehmen, macht eigene Composables konsistent mit dem breiteren Vue-Ökosystem und reduziert Überraschungen für Teammitglieder, die andere Composables gewohnt sind.
Ein zusätzliches Muster: Composables, die sowohl einen internen reaktiven Zustand als auch abgeleitete computed()-Werte zurückgeben, kombinieren häufig toRefs() für den Basiszustand mit direkter Rückgabe der computed()-Referenzen, da computed()-Werte bereits von sich aus Refs sind und keine zusätzliche Konvertierung benötigen.
import { reactive, computed, toRefs } from 'vue'
export function useShoppingCart() {
const state = reactive({
items: [],
discountPercent: 0
})
// computed refs need no extra conversion — they are refs already
const total = computed(() =>
state.items.reduce((sum, item) => sum + item.price, 0) *
(1 - state.discountPercent / 100)
)
function addItem(item) {
state.items.push(item)
}
return {
...toRefs(state), // items and discountPercent stay reactive when destructured
total, // already a ref, returned as-is
addItem
}
}
9. toRef() vs. toRefs() vs. direkte Destrukturierung
Die folgende Übersicht fasst zusammen, welches Werkzeug für welchen Anwendungsfall die richtige Wahl ist.
| Anwendungsfall | Direkte Destrukturierung | toRef() / toRefs() | Ergebnis |
|---|---|---|---|
| Ein einzelnes Feld herauslösen | Reaktivität geht verloren | toRef(state, 'feld') | Feld bleibt an Quelle gekoppelt |
| Composable-Rückgabe destrukturieren | Eingefrorene Werte für Konsumenten | toRefs(state) am Rückgabepunkt | Konsumenten destrukturieren sicher |
| Einfache Transformation eines Werts | Eigener computed() nötig | toRef(() => ...) seit Vue 3.3 | Kompakter für günstige Berechnungen |
| Objekt als Ganzes weiterreichen | Kein Problem, Referenz bleibt reaktiv | Unnötiger Umweg | toRef/toRefs nur bei Zerlegung nötig |
| Teure Berechnung mit Caching | Kein Caching bei jedem Zugriff | computed() statt Getter-toRef() | Vermeidet wiederholte teure Berechnung |
Die Faustregel: Solange ein reaktives Objekt als Ganzes weitergereicht wird, ist keine Konvertierung nötig. Sobald einzelne Felder herausgelöst oder destrukturiert werden sollen, sind toRef() für einzelne Properties und toRefs() für das gesamte Objekt die richtigen Werkzeuge, um die Kopplung an die ursprüngliche reaktive Quelle zu erhalten.
Mironsoft
Vue 3 Composables und API-Design Beratung
Verliert eure Reaktivität beim Destrukturieren?
Wir prüfen eure Composable-APIs auf saubere toRef/toRefs-Konventionen und beheben stille Reaktivitätsverluste, bevor sie zu schwer auffindbaren Bugs werden.
API-Audit
Composable-Rückgaben auf fehlendes toRefs() prüfen
Konsistenz-Refactoring
Einheitliche toRef/toRefs-Konventionen im gesamten Team
Team-Schulung
Sicheres Destrukturieren reaktiver Objekte vermitteln
10. Zusammenfassung
toRef() und toRefs() lösen ein sehr konkretes Problem: Eine normale Destrukturierung eines reactive()-Objekts zerstört die Reaktivität einzelner Felder, weil sie zu einfachen Kopien werden. toRef(source, key) erzeugt einen einzelnen Ref, der bidirektional an die Property der Quelle gekoppelt bleibt, toRefs(source) tut dasselbe für alle Properties eines Objekts gleichzeitig und ist damit das Standardwerkzeug am Ende fast jedes Composables, das intern mit reactive() arbeitet.
Seit Vue 3.3 akzeptiert toRef() zusätzlich eine Getter-Funktion für read-only, ungecachte Transformationen, eine kompakte Alternative zu computed() für günstige Berechnungen. Der historische Fehler, dass toRef() für noch nicht existierende Properties nicht funktionierte, wurde in derselben Version behoben. Wer diese Konvention konsequent in eigenen Composables anwendet, vermeidet stille Reaktivitätsverluste und bleibt konsistent mit dem Rest des Vue-Ökosystems.
toRef() und toRefs() in Vue 3 — Das Wichtigste auf einen Blick
Grundprinzip
Erzeugt Refs, die bidirektional an eine Property eines reactive()-Objekts gekoppelt bleiben.
toRefs()
Wandelt alle Properties eines Objekts gleichzeitig in gekoppelte Refs um, Standard am Composable-Rückgabepunkt.
Getter-toRef() (Vue 3.3+)
Read-only Ref aus einer Getter-Funktion, ungecached, kompakter als computed() für einfache Fälle.
Häufigster Fehler
Direkte Destrukturierung eines reactive()-Objekts ohne toRefs(), Reaktivität geht dabei verloren.