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.
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.
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(); // ✅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.
// $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.
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.
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
$stateist 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.