S SvelteKurs
M2 · Runes & Reaktivität

$effect — Seiteneffekte

Für alles, was über reinen State hinausgeht: DOM manipulieren, Timer setzen, loggen, in localStorage schreiben oder externe Bibliotheken ansteuern — automatisch bei jeder relevanten Änderung.

Was ist ein Seiteneffekt?

$state hält Zustand, $derived leitet Werte ab — beide bleiben innerhalb deiner Komponente. Ein Seiteneffekt ist alles, was mit der Außenwelt spricht: den Dokumenttitel setzen, einen Timer starten, in localStorage schreiben, eine Chart-Bibliothek aktualisieren oder etwas loggen. Dafür gibt es $effect.

svelte
<script>
  let count = $state(0);

  // Läuft nach dem Mounten und immer, wenn sich count ändert:
  $effect(() => {
    document.title = `${count} Klicks`;
  });
</script>

Ein $effect läuft einmal, nachdem die Komponente gerendert wurde, und danach erneut, sobald sich eine seiner Abhängigkeiten ändert.

Automatisches Dependency-Tracking

Wie bei $derived musst du Abhängigkeiten nicht angeben. Svelte merkt sich jeden reaktiven Wert, den du während des Laufs liest, und startet den Effekt genau dann neu, wenn sich einer davon ändert.

js
let a = $state(1);
let b = $state(2);

$effect(() => {
  // Nur `a` wird gelesen → nur `a` ist Abhängigkeit.
  // Ändert sich `b`, läuft dieser Effekt NICHT neu.
  console.log('a =', a);
});
▶ $effect als Änderungs-Protokoll · Interaktiv

Effekt-Log (neueste zuerst):

Jeder Tastendruck ändert name — der Effekt läuft automatisch und schreibt eine neue Zeile.

Aufräumen mit einer Rückgabefunktion

Startet ein Effekt etwas Langlebiges — einen Timer, eine Subscription, einen Event-Listener — muss das wieder abgebaut werden. Dafür gibst du aus dem Effekt eine Cleanup-Funktion zurück. Sie läuft, bevor der Effekt erneut startet, und wenn die Komponente verschwindet.

js
let laeuft = $state(false);

$effect(() => {
  if (!laeuft) return;

  const id = setInterval(() => console.log('tick'), 1000);

  // Die Rückgabefunktion räumt auf: vor dem nächsten Lauf
  // und beim Entfernen der Komponente.
  return () => clearInterval(id);
});
▶ Timer mit Cleanup · Interaktiv
0s

Startet laeuft auf true, legt der Effekt ein setInterval an. Beim Stoppen räumt die Rückgabefunktion es per clearInterval wieder ab — kein Speicherleck.

Praktische Einsätze

Typische Fälle: einen Wert in localStorage spiegeln, eine externe Bibliothek synchron halten oder auf DOM-Ereignisse reagieren.

js
let theme = $state('light');

// Persistiert den Wert automatisch bei jeder Änderung:
$effect(() => {
  localStorage.setItem('theme', theme);
});

$effect.pre — vor dem DOM-Update

Standardmäßig läuft $effect nach dem DOM-Update. Manchmal brauchst du den Zustand davor — etwa um die Scroll-Position zu lesen, bevor neue Elemente gerendert werden. Dafür gibt es $effect.pre, das ansonsten identisch funktioniert.

svelte
<script>
  let nachrichten = $state([]);
  let liste;

  // $effect.pre läuft VOR dem DOM-Update — praktisch, um z.B.
  // die Scroll-Position zu lesen, bevor neue Einträge gerendert werden.
  $effect.pre(() => {
    nachrichten.length; // als Abhängigkeit lesen
    if (!liste) return;
    const amEnde = liste.scrollTop + liste.clientHeight >= liste.scrollHeight;
    if (amEnde) queueMicrotask(() => (liste.scrollTop = liste.scrollHeight));
  });
</script>

Wann du $effect NICHT nutzen solltest

$effect ist mächtig — und wird deshalb gern übernutzt. Der häufigste Fehler: einen Effekt zu verwenden, um einen State-Wert aus einem anderen zu berechnen. Das ist ein abgeleiteter Wert und gehört in ein $derived.

js
let vorname = $state('Anna');
let nachname = $state('Berg');
let vollname = $state('');

// ❌ FALSCH: State aus anderem State per Effekt synchronisieren
$effect(() => {
  vollname = `${vorname} ${nachname}`;
});

// ✅ RICHTIG: das ist ein abgeleiteter Wert
let vollname2 = $derived(`${vorname} ${nachname}`);

⚠️ Häufige Fehlnutzung: State-Synchronisation

Wenn ein Effekt am Ende nur einen anderen $state setzt, ist es fast immer ein verkapptes $derived. Die Effekt-Variante ist umständlicher, fehleranfälliger (Endlosschleifen!) und läuft einen Tick zu spät. Faustregel: Berechnest du einen Wert, nimm $derived. Erst wenn du wirklich mit der Außenwelt sprichst, ist $effect richtig.

Rails-Vergleich

$effect erinnert an ActiveRecord-Callbacks wie after_save: Code, der als Nebenwirkung läuft, wenn etwas passiert.

🛤️ Rails-Vergleich: Callbacks vs. $effect

Ein after_save-Callback feuert bei einem konkreten Ereignis (dem Speichern). Ein $effect feuert automatisch, sobald sich irgendeine gelesene Abhängigkeit ändert — du beschreibst nur was passieren soll, nicht wann. Und wie ein sauber geschriebener Callback räumt ein guter Effekt hinter sich auf.

Rails ruby
class Article < ApplicationRecord
  # Läuft als Nebenwirkung nach dem Speichern:
  after_save :reindex_search, :notify_subscribers

  private

  def reindex_search
    SearchIndex.update(self)   # externer Effekt
  end
end

# Der Callback feuert bei einem konkreten Ereignis (save).
# $effect feuert automatisch, wenn sich eine gelesene
# Abhängigkeit ändert — du sagst nur WAS, nicht WANN.

Zusammenfassung

💡 Das Wichtigste

  • $effect(() => { … }) läuft nach dem Mounten und bei jeder Änderung einer gelesenen Abhängigkeit.
  • Abhängigkeiten werden automatisch erkannt.
  • Eine Rückgabefunktion räumt auf (Timer, Listener, Subscriptions).
  • $effect.pre läuft vor dem DOM-Update.
  • Nicht für abgeleitete Werte oder State-Sync nutzen — dafür ist $derived da.

🎯 Probier es selbst

Erweitere die Timer-Demo so, dass der Dokumenttitel (document.title) laufend die verstrichenen Sekunden anzeigt. Nutze dafür einen zweiten $effect, der sekunden liest. Beobachte im Browser-Tab, wie der Titel mitzählt.