Vue Reactivity mit Map und Set praktisch nutzen: Auswahl-Sets und Lookup-Maps
AI generated
{ }
Vue.js · Reactivity · Map · Set · Performance
Reactive Collections
mit Map und Set praktisch nutzen: Auswahl-Sets und Lookup-Maps

Die meisten Vue-Tutorials arbeiten fast ausschließlich mit Arrays und einfachen Objects, dabei bringt JavaScript mit Map und Set zwei Collection-Typen mit, die für bestimmte Aufgaben deutlich besser passen. Vues Reactivity-System unterstützt beide vollständig, sodass reactive(new Map()) und reactive(new Set()) genauso zuverlässig auf Änderungen reagieren wie ein reaktives Array. Wer weiß, wann eine Set-basierte Auswahl in einer Multi-Select-UI oder eine Map-basierte Lookup-Struktur die bessere Wahl ist, und welche Fallstricke bei der Destrukturierung lauern, schreibt spürbar robusteren und schnelleren Code.

15 Min. Lesezeit reactive() · Map · Set Vue 3 · Composition API · Performance

1. Warum Map und Set neben Arrays in Vue-Projekten relevant sind

Ein Array ist die naheliegende Wahl für eine Liste von Elementen, verliert aber schnell an Komfort, sobald zwei Anforderungen gleichzeitig gefragt sind: eindeutige Werte ohne manuelle Duplikatprüfung, und schneller Zugriff per Schlüssel statt per Index oder linearer Suche. Ein Set garantiert von Haus aus, dass jeder Wert nur einmal enthalten sein kann, was es zur natürlichen Datenstruktur für Auswahlzustände macht, bei denen jedes Element entweder ausgewählt oder nicht ausgewählt ist. Eine Map wiederum bildet Schlüssel auf Werte ab und erlaubt dabei beliebige Schlüsseltypen, nicht nur Strings wie bei einem klassischen Object.

In der Praxis begegnen einem beide Strukturen regelmäßig: ein Set für die IDs ausgewählter Zeilen in einer Tabelle, eine Map für ein Lookup von Produkt-ID auf vollständiges Produktobjekt, um wiederholte Array.find()-Aufrufe zu vermeiden. Vue behandelt beide Typen als vollwertige Bürger im Reactivity-System, das bedeutet, dass set(), add(), delete() und clear() alle registrierten Effekte auf dieselbe Weise auffrischen wie push() oder splice() bei einem reaktiven Array.

2. reactive(new Map()) im Detail: warum es funktioniert

Vues reactive() erzeugt einen Proxy um das übergebene Objekt, und dieser Proxy überwacht bei Map und Set nicht nur den lesenden Zugriff auf einzelne Einträge, sondern auch die mutierenden Methoden der jeweiligen Collection selbst. Ruft man auf einer reaktiven Map map.set(key, value) auf, erkennt Vue intern, dass sich der Zustand der Collection geändert hat, und benachrichtigt alle Effekte, die zuvor lesend auf diese Map zugegriffen haben, etwa über map.get(key), map.has(key) oder map.size.

Wichtig dabei ist, dass die Reaktivität an der reactive()-Proxy-Instanz selbst hängt, nicht an der zugrunde liegenden, ursprünglichen Map. Wird die Map in einem ref() gehalten statt direkt mit reactive() gewrappt, muss innerhalb von script setup weiterhin .value verwendet werden, um an die eigentliche Map-Instanz zu gelangen, bevor set(), get() oder size angesprochen werden können. Im Template dagegen entfällt .value automatisch durch das eingebaute Auto-Unwrapping von Refs auf oberster Ebene.


import { reactive } from 'vue'

interface Product {
  id: string
  name: string
  price: number
}

const productLookup = reactive(new Map<string, Product>())

function addProduct(product: Product) {
  productLookup.set(product.id, product)
}

function removeProduct(id: string) {
  productLookup.delete(id)
}

// productLookup.size und productLookup.get(id) sind vollständig reaktiv

3. Vorsicht bei Destrukturierung: was die Reaktivität bricht

Der häufigste Stolperstein bei reaktiven Map- und Set-Instanzen ist die Destrukturierung, weil Methoden wie map.get oder map.has als eigenständige Funktionsreferenzen aus dem Proxy herausgelöst ihren internen Bezug auf this verlieren. Ein Aufruf wie const { get, set } = productLookup gefolgt von get('abc') führt zur Laufzeit zu einem TypeError, weil get intern erwartet, über die Proxy-Instanz aufgerufen zu werden, um korrekt auf die zugrunde liegende Map zuzugreifen.

Der sichere Weg ist, die Map- oder Set-Instanz als Ganzes zu referenzieren und Methoden stets direkt darauf aufzurufen, also productLookup.get(id) statt einer vorab destrukturierten get-Funktion. Wer eine einzelne, reaktive Ableitung braucht, etwa nur die aktuelle Größe, sollte dafür ein computed() verwenden, das intern weiterhin productLookup.size liest, statt size einmalig zu destrukturieren und damit auf einen statischen Schnappschuss einzufrieren.


// Falsch: bricht die Reaktivität und den this-Bezug
const { get, has } = productLookup
// get('abc') wirft zur Laufzeit einen TypeError

// Richtig: Methoden direkt auf der Instanz aufrufen
productLookup.get('abc')
productLookup.has('abc')

// Richtig für eine abgeleitete, reaktive Größe
import { computed } from 'vue'
const productCount = computed(() => productLookup.size)

4. Praxisbeispiel: eindeutige Auswahl-Sets in einer Multi-Select-UI

In einer Tabelle mit Mehrfachauswahl, etwa für Bulk-Aktionen auf mehreren Zeilen, ist ein Set die passendste Datenstruktur für die ausgewählten IDs, weil Doppelauswahl strukturell ausgeschlossen ist und Zugehörigkeitsprüfungen per has() in konstanter Zeit erfolgen. Ein Array würde entweder manuelle Duplikatprüfung vor jedem push() erfordern, oder bei jeder Sichtbarkeitsprüfung eine lineare includes()-Suche durch die gesamte Liste, was bei großen Tabellen spürbar langsamer wird.

Die Toggle-Logik für eine einzelne Zeile reduziert sich mit einem Set auf drei Zeilen: prüfen, ob die ID bereits enthalten ist, und je nach Ergebnis add() oder delete() aufrufen. Für Select-All-Funktionalität lässt sich das Set einfach per clear() leeren oder mit allen sichtbaren IDs neu befüllen, wobei sich reactive(new Set()) in dieser Konstellation besonders anbietet, weil das Template dann direkt selectedIds.has(row.id) für jede Zeile abfragen kann, ohne zusätzliche computed-Werte anzulegen.


import { reactive } from 'vue'

const selectedIds = reactive(new Set<string>())

function toggleSelection(id: string) {
  if (selectedIds.has(id)) {
    selectedIds.delete(id)
  } else {
    selectedIds.add(id)
  }
}

function selectAll(ids: string[]) {
  ids.forEach((id) => selectedIds.add(id))
}

function clearSelection() {
  selectedIds.clear()
}

5. Praxisbeispiel: Lookup-Maps für schnellen Zugriff auf Detaildaten

Sobald eine Komponente wiederholt anhand einer ID auf ein Objekt in einer größeren Liste zugreifen muss, etwa um in einer Warenkorb-Ansicht zu jeder Position die vollständigen Produktdaten nachzuschlagen, lohnt sich eine begleitende Lookup-Map neben dem eigentlichen Array. Statt bei jedem Zugriff products.find(p => p.id === id) durch das gesamte Array zu iterieren, liefert productMap.get(id) das Ergebnis in konstanter Zeit, unabhängig von der Größe der ursprünglichen Liste.

Diese Lookup-Map wird typischerweise als computed() aus der ursprünglichen Datenquelle abgeleitet, etwa aus einem per API geladenen Array, damit sie automatisch neu aufgebaut wird, sobald sich die Datenquelle ändert, ohne dass zwei Kopien der Daten manuell synchron gehalten werden müssen. Für häufig aktualisierte, große Listen mit mehreren hundert oder tausend Einträgen macht dieser Unterschied in der Praxis spürbar aus, ob eine Detailansicht sofort reagiert oder mit jedem zusätzlichen Eintrag merklich träger wird.

6. Performance-Vergleich: Map-Lookup gegen Array.find()

Algorithmisch betrachtet liegt der Unterschied zwischen einer Map und einem Array bei der Suche in der Komplexität: Array.find() durchsucht im schlechtesten Fall jedes Element linear, also mit einer Komplexität von O(n), während map.get() dank Hashing im Durchschnitt in konstanter Zeit, also O(1), arbeitet, unabhängig von der Anzahl der Einträge. Bei kleinen Listen mit wenigen Dutzend Einträgen ist dieser Unterschied in der Praxis kaum messbar, weil moderne JavaScript-Engines auch kleine lineare Suchen extrem schnell abarbeiten.

Relevant wird der Unterschied erst bei größeren Datenmengen oder bei Zugriffsmustern, die denselben Lookup sehr häufig innerhalb kurzer Zeit wiederholen, etwa beim Rendern einer langen Tabelle, in der für jede Zeile ein Lookup in einer verwandten Liste stattfindet. In diesem Szenario summieren sich viele einzelne O(n)-Suchen schnell zu einer insgesamt quadratischen Laufzeit, während dieselbe Aufgabe mit einer vorab aufgebauten Map linear bleibt, was den einmaligen Aufwand für den Aufbau der Map in aller Regel mehr als aufwiegt.

7. ref() versus reactive() für Map und Set: was ist sinnvoller

Sowohl ref(new Map()) als auch reactive(new Map()) machen eine Map reaktiv, unterscheiden sich aber im praktischen Umgang. Bei reactive() wird direkt auf der zurückgegebenen Instanz gearbeitet, also productLookup.set(id, value) ohne .value, was sich im Skript-Code intuitiver liest und der üblichen Verwendung von reactive() für objektartige Strukturen entspricht. Bei ref() muss dagegen konsequent .value vorangestellt werden, etwa productLookup.value.set(id, value), was zwar mehr Tipparbeit bedeutet, aber den Vorteil hat, dass sich die gesamte Map jederzeit unkompliziert durch eine komplett neue Instanz ersetzen lässt, etwa productLookup.value = new Map(freshEntries).

In der Praxis ist reactive() für Map und Set meist die angenehmere Wahl, solange die Collection ausschließlich über ihre eigenen Methoden verändert wird und niemals als Ganzes neu zugewiesen werden muss. Sobald ein Anwendungsfall regelmäßig die komplette Collection austauscht, etwa beim Neuladen aller Daten nach einem API-Call, ist ref() die robustere Wahl, weil eine mit reactive() gewrappte Variable nicht neu zugewiesen werden kann, ohne die ursprüngliche Reaktivitätsverbindung zu verlieren.

8. Watchen von Map- und Set-Änderungen: die deep-Option und ihre Grenzen

Ein watch() auf eine reaktive Map oder ein reaktives Set reagiert standardmäßig nur auf eine komplette Neuzuweisung der Referenz, nicht auf einzelne set()-, add()- oder delete()-Aufrufe, weil sich die Referenz auf das Proxy-Objekt selbst dabei nicht ändert. Um auf interne Mutationen zu reagieren, muss watch() mit der Option { deep: true } aufgerufen werden, wodurch Vue interne Änderungen an der Collection ebenfalls erkennt und den Callback entsprechend auslöst, auch ohne dass sich die äußere Referenz ändert.

Bei sehr großen Maps oder Sets kann { deep: true } spürbaren Overhead verursachen, weil Vue bei jeder Mutation die interne Struktur überprüfen muss. Für performancekritische Fälle ist es oft sinnvoller, gezielt einzelne, davon abgeleitete computed-Werte zu beobachten, etwa computed(() => productLookup.size), statt die gesamte Collection tief zu watchen, weil sich ein computed automatisch und effizient nur dann neu berechnet, wenn tatsächlich gelesene Abhängigkeiten sich geändert haben.

9. Häufige Fehler und Fazit

Der häufigste Fehler ist die eingangs beschriebene Destrukturierung von Methoden, die den this-Bezug bricht und zur Laufzeit zu einem TypeError führt, obwohl der Code auf den ersten Blick unauffällig aussieht. Ein zweiter häufiger Fehler ist, ein watch() ohne { deep: true } auf eine Map oder ein Set zu setzen und sich zu wundern, warum interne Änderungen keine Reaktion auslösen, während eine komplette Neuzuweisung der Collection problemlos funktioniert.

Als Fazit gilt: Map und Set sind kein exotisches Nischenwerkzeug, sondern in Vue 3 vollwertig unterstützte Reactivity-Primitive, die für Auswahlzustände und Lookup-Strukturen oft die klarere und schnellere Wahl gegenüber Arrays sind. Wer Methoden stets direkt auf der Instanz aufruft, reactive() für inkrementell veränderte Collections und ref() für regelmäßig komplett ersetzte Collections nutzt, und bei Bedarf gezielt mit { deep: true } oder computed() arbeitet, holt aus beiden Strukturen das volle Potenzial heraus.

Struktur Typischer Einsatz Zugriffszeit Reaktivität in Vue
Array Geordnete Liste, Iteration, Rendering O(n) für Suche per find() Voll unterstützt via reactive()
Set Eindeutige Auswahl, Zugehörigkeitsprüfung O(1) für has() Voll unterstützt via reactive()
Map Schlüssel-Wert-Lookup, Detaildaten nach ID O(1) für get() Voll unterstützt via reactive()
Object Einfache Schlüssel-Wert-Paare mit String-Keys O(1) für direkten Property-Zugriff Voll unterstützt via reactive()

Mironsoft

Vue-Architektur, Composition API und Nuxt-Performance

Vue-Anwendungen, die mit jedem Feature nicht komplizierter werden?

Wir prüfen bestehende Vue- und Nuxt-Projekte auf unstrukturierte Composables, ungenutzte Reaktivität und aufgeblähte Bundles und bauen daraus eine Architektur, die neue Features aufnimmt, ohne die Codebasis unübersichtlicher zu machen.

Architektur-Review

Composables, State-Management und Komponentenstruktur auf Wartbarkeit prüfen.

Performance-Audit

Reaktivitäts-Overhead, Bundle-Größe und Nuxt-Rendering-Strategie systematisch optimieren.

Nuxt-Integration

SSR/SSG-Setup und API-Anbindung robust und typsicher aufbauen.

10. Zusammenfassung

Reactive Map und Set in Vue: Das Wichtigste auf einen Blick

Set für Auswahl

Garantiert eindeutige Werte und liefert has()-Prüfungen in konstanter Zeit, ideal für Multi-Select-Zustände.

Map für Lookup

Bildet Schlüssel auf Werte ab und ersetzt lineares Array.find() durch konstante get()-Zugriffe.

Destrukturierungsfalle

Methoden nie destrukturieren, immer direkt auf der reaktiven Instanz aufrufen, sonst bricht der this-Bezug.

watch mit deep

Interne Mutationen wie set() oder add() erfordern watch() mit der Option { deep: true }.

11. FAQ: Reactive Map und Set in Vue: Das Wichtigste auf einen Blick

1Funktioniert reactive() direkt mit new Map() und new Set()?
Ja, Vue 3 unterstützt Map und Set als vollwertige Reactivity-Primitive. reactive(new Map()) und reactive(new Set()) reagieren auf set(), add(), delete() und clear() genauso zuverlässig wie ein reaktives Array auf push() oder splice().
2Warum wirft die destrukturierte get-Methode einer reaktiven Map einen Fehler?
Weil die Methode als eigenständige Funktion aus dem Proxy herausgelöst ihren internen this-Bezug zur Map-Instanz verliert. Der Aufruf muss immer direkt auf der Instanz erfolgen, also map.get(key) statt einer vorher destrukturierten get-Funktion.
3Wann ist ein Set besser als ein Array für eine Auswahl?
Immer dann, wenn Duplikate strukturell ausgeschlossen sein sollen und häufig geprüft wird, ob ein bestimmter Wert bereits enthalten ist. Ein Set garantiert Eindeutigkeit automatisch und beantwortet has()-Abfragen in konstanter Zeit.
4Wie schnell ist map.get() im Vergleich zu Array.find()?
map.get() arbeitet dank interner Hashing-Struktur im Durchschnitt in konstanter Zeit, also O(1), unabhängig von der Anzahl der Einträge. Array.find() durchsucht im schlechtesten Fall jedes Element linear, also mit O(n).
5Sollte man für eine Map eher ref() oder reactive() verwenden?
reactive() ist meist angenehmer, solange die Map nur über ihre eigenen Methoden verändert wird. ref() ist sinnvoller, wenn die komplette Map regelmäßig durch eine neue Instanz ersetzt werden soll, etwa nach dem Neuladen aller Daten.
6Reagiert watch() automatisch auf set()-Aufrufe auf einer reaktiven Map?
Nein, standardmäßig nicht, weil sich die äußere Referenz auf das Proxy-Objekt dabei nicht ändert. Für interne Mutationen muss watch() mit der Option { deep: true } aufgerufen werden.
7Wie baut man eine performante Lookup-Map aus einem geladenen Array auf?
Am besten mit computed(), das aus dem ursprünglichen Array eine Map aus ID und Objekt ableitet. So wird die Map automatisch neu aufgebaut, sobald sich die Datenquelle ändert, ohne zwei Kopien manuell synchron zu halten.
8Verursacht { deep: true } bei watch() spürbaren Overhead?
Bei sehr großen Maps oder Sets kann das messbar werden, weil Vue bei jeder Mutation die interne Struktur überprüft. Für performancekritische Fälle ist ein gezieltes computed() auf einen abgeleiteten Wert oft die effizientere Alternative.
9Kann eine reaktive Map beliebige Schlüsseltypen verwenden?
Ja, im Gegensatz zu einem klassischen Object mit ausschließlich String- oder Symbol-Keys erlaubt eine Map beliebige Werte als Schlüssel, etwa Objekte oder andere Referenzen, und bleibt dabei vollständig reaktiv.
10Lohnt sich der Umstieg von Array auf Map bei kleinen Listen?
Kaum. Bei wenigen Dutzend Einträgen ist der Performance-Unterschied praktisch nicht messbar. Der Umstieg zahlt sich vor allem bei größeren, häufig abgefragten Listen oder bei Zugriffsmustern mit vielen wiederholten Lookups aus.