S SvelteKurs
M2 · Runes & Reaktivität

Tiefe Reaktivität

Warum du verschachtelte Objekte und Arrays einfach mutieren darfst — und wann du mit $state.raw und $state.snapshot bewusst ausbrichst.

Reaktivität geht bis in die Tiefe

$state ist nicht auf einzelne Werte beschränkt. Steckst du ein Objekt oder Array hinein, wird es tief reaktiv: Auch Änderungen an verschachtelten Eigenschaften und Array-Methoden wie push oder sort werden erkannt und aktualisieren die Oberfläche.

js
let aufgabe = $state({
  titel: 'Svelte lernen',
  tags: ['frontend'],
  details: { prioritaet: 'hoch' }
});

// Alle drei sind reaktiv — auch tief verschachtelt:
aufgabe.titel = 'Runes meistern';
aufgabe.tags.push('reaktivität');
aufgabe.details.prioritaet = 'mittel';

Du musst nichts unveränderlich behandeln, keine Kopie erzeugen, keinen Setter aufrufen — du mutierst ganz normal, und Svelte folgt.

Wie funktioniert das? Proxies

Unter der Haube verpackt Svelte deine Objekte und Arrays in einen Proxy. Ein Proxy fängt jeden Zugriff ab: Beim Lesen merkt sich Svelte, welcher Effekt oder abgeleitete Wert davon abhängt; beim Schreiben löst es gezielt die betroffenen Updates aus. Deshalb funktionieren auch tiefe Mutationen ohne dein Zutun.

js
let liste = $state([1, 2, 3]);

// Svelte verpackt Objekte/Arrays in einen Proxy und fängt
// JEDEN Zugriff ab — Lesen registriert Abhängigkeiten,
// Schreiben löst Updates aus. Deshalb funktioniert das:
liste.push(4);      // ✅
liste[0] = 99;      // ✅
liste.sort();       // ✅
▶ Tief verschachteltes Objekt live mutieren · Interaktiv
#frontend #svelte
Priorität:

Jede Mutation — Checkbox, Tag, verschachtelte Priorität — aktualisiert die Anzeige sofort, obwohl wir das Objekt direkt verändern.

$state.raw — bewusst nicht tief

Die Proxy-Verpackung hat einen kleinen Preis. Bei sehr großen Datenstrukturen, die du ohnehin nur komplett ersetzt (statt zu mutieren), lohnt sich $state.raw. Der Wert bleibt reaktiv, aber innere Mutationen werden nicht verfolgt — Änderungen wirken nur, wenn du den Wert komplett neu zuweist.

js
// $state.raw: NICHT tief. Der Wert ist reaktiv, aber
// Mutationen im Inneren werden NICHT verfolgt.
let punkte = $state.raw({ x: 0, y: 0 });

punkte.x = 10;               // ❌ löst KEIN Update aus
punkte = { x: 10, y: 0 };    // ✅ Ersetzen statt Mutieren

// Nützlich für große, unveränderliche Datenmengen —
// spart den Overhead der Proxy-Verpackung.

ℹ️ Mutieren vs. Ersetzen

Merksatz: Mit normalem $state darfst du mutieren. Mit $state.raw musst du ersetzen — Svelte reagiert nur auf die Zuweisung selbst, nicht auf Änderungen im Inneren.

$state.snapshot — ein statischer Klon

Weil ein $state-Objekt ein Proxy ist, verhält es sich in ein paar Situationen unerwartet: Ein console.log zeigt den Proxy statt der Daten, und externe APIs, die structuredClone verwenden (z. B. beim Versenden über Web-Worker oder fetch), können mit einem Proxy nicht umgehen. $state.snapshot() gibt dir einen echten, entkoppelten Klon ohne Proxy zurück.

js
let aufgabe = $state({ tags: ['a', 'b'] });

// Ein $state-Objekt ist ein Proxy. Loggt man es direkt,
// sieht man im Devtools-Log den Proxy, nicht die Daten.
console.log(aufgabe);                    // Proxy(Object) …

// $state.snapshot() gibt einen statischen, entkoppelten Klon:
console.log($state.snapshot(aufgabe));   // { tags: ['a', 'b'] }

// Pflicht, bevor du State an externe APIs / structuredClone /
// fetch-Bodies übergibst, die keinen Proxy erwarten.
await fetch('/api', {
  method: 'POST',
  body: JSON.stringify($state.snapshot(aufgabe))
});

💡 Wann snapshot?

Immer dann, wenn der Wert die reaktive Welt verlässt: fürs Loggen, für JSON.stringify, structuredClone, das Übergeben an eine externe Bibliothek oder einen fetch-Body. In der Demo oben schreibt der Button einen sauberen Snapshot in die Konsole — öffne die Devtools und vergleiche.

Rails-Vergleich

Das Verfolgen von Änderungen an einem Objekt kennst du aus ActiveRecord: Dirty Tracking und nested_attributes.

🛤️ Rails-Vergleich: Dirty Tracking & nested attributes

ActiveRecord merkt sich über Dirty Tracking, welche Attribute (und verschachtelten Records) sich geändert haben — sichtbar über changed? und changes. Svelte-Proxies machen im Prinzip dasselbe im Browser, nur unmittelbar: Jede Mutation aktualisiert sofort die UI, statt erst beim save ausgewertet zu werden. Und wie bei as_json, bevor du ein Model nach außen gibst, holst du dir mit $state.snapshot eine reine Datenkopie ohne den reaktiven Wrapper.

Rails ruby
class Task < ApplicationRecord
  has_many :tags
  accepts_nested_attributes_for :tags
end

task = Task.find(1)
task.title = 'neu'
task.title_changed?   # => true  (Dirty Tracking)
task.changes          # => { "title" => ["alt", "neu"] }

# ActiveRecord verfolgt Änderungen an Attributen und
# verschachtelten Records. Svelte-Proxies machen dasselbe
# im Browser — nur dass jede Mutation sofort die UI aktualisiert,
# nicht erst beim save.

Zusammenfassung

💡 Das Wichtigste

  • $state ist tief reaktiv: verschachtelte Objekte und Arrays inklusive.
  • Möglich macht das ein Proxy, der jeden Zugriff abfängt.
  • $state.raw: nicht tief — ersetzen statt mutieren, gut für große Datenmengen.
  • $state.snapshot: statischer Klon ohne Proxy — für Logging und externe APIs.

🎯 Probier es selbst

Ergänze die Aufgaben-Demo um ein Feld geschaetzt (Zahl) in details mit zwei Buttons „+1" und „−1". Beobachte, dass die tiefe Mutation aufgabe.details.geschaetzt++ die Anzeige sofort aktualisiert — ganz ohne zusätzliche Verdrahtung.