Das Modul svelte/store bietet drei Helfer, mit denen du in 90 % der Fälle keinen eigenen Store schreiben musst: writable für veränderliche Werte, readable für nur lesbare Werte mit eigener Setup-Logik und derived für aus anderen Stores berechnete Werte. Dieser Artikel zeigt jeden mit einem realistischen Beispiel und einer klaren Erklärung, wann welcher passt.

writable – der Standard

writable(initialValue) erzeugt einen Store mit drei Methoden:

  • subscribe(fn) — abonniert den Store (kennen wir aus dem Einführungs-Artikel).
  • set(value) — setzt einen neuen Wert.
  • update(fn) — berechnet einen neuen Wert basierend auf dem alten.
ts src/lib/stores/counter.ts
import { writable } from 'svelte/store';

export const count = writable(0);
svelte Verwendung im Markup
<script>
    import { count } from '$lib/stores/counter';
</script>

<p>{$count}</p>
<button onclick={() => $count++}>+1</button>
<button onclick={() => count.set(0)}>Reset</button>

$count++ ruft intern count.update(n => n + 1) auf. count.set(0) setzt den Wert direkt — auch außerhalb der $-Syntax.

update mit aktuellem Wert

Wenn der neue Wert vom alten abhängt, ist update die saubere Form:

ts update-Beispiel
import { writable } from 'svelte/store';

export const todos = writable([]);

export function addTodo(text) {
    todos.update((current) => [...current, { id: Date.now(), text }]);
}

writable mit Start-/Stopp-Funktion

Beim Erzeugen von writable kannst du eine zweite Funktion mitgeben — die Lifecycle-Funktion:

ts Mit Lifecycle
import { writable } from 'svelte/store';

export const time = writable(new Date(), (set) => {
    // Wird ausgeführt, sobald der erste Subscriber aktiv wird
    const id = setInterval(() => set(new Date()), 1000);

    // Wird ausgeführt, wenn der letzte Subscriber abmeldet
    return () => clearInterval(id);
});

Wichtig zu verstehen:

  • Die Setup-Funktion läuft erst, wenn jemand abonniert — nicht beim Modul-Import.
  • Sie läuft nur einmal, solange mindestens ein Subscriber aktiv ist.
  • Die Cleanup-Funktion läuft, wenn der letzte Subscriber abmeldet.

So vermeidet man Timer und Listener, die laufen, ohne dass jemand zuhört.

readable – nur lesen, intern ändern

readable ist im Prinzip ein writable ohne set und update nach außen. Stattdessen wird der Wert intern in der Setup-Funktion verändert. So lassen sich Datenströme modellieren, deren Quelle die Komponenten nicht direkt manipulieren sollen.

ts Live-Uhr als readable
import { readable } from 'svelte/store';

export const now = readable(new Date(), (set) => {
    const id = setInterval(() => set(new Date()), 1000);
    return () => clearInterval(id);
});
svelte Verwendung
<script>
    import { now } from '$lib/stores/clock';
</script>

<p>{$now.toLocaleTimeString()}</p>

Eine Komponente kann $now lesen — aber nicht überschreiben. Damit ist die Verantwortung klar: Der Store kümmert sich um die Aktualisierung, die Komponente nur um die Anzeige.

derived – Werte aus anderen Stores berechnen

derived(stores, fn) erzeugt einen Store, der sich automatisch aktualisiert, wenn sich seine Quell-Stores ändern. Klassischer Anwendungsfall: einen abgeleiteten Wert berechnen.

Aus einem Store

ts Verdoppelter Wert
import { writable, derived } from 'svelte/store';

export const count = writable(0);
export const doubled = derived(count, ($count) => $count * 2);

doubled ist read-only — es leitet sich aus count ab. Sobald count sich ändert, läuft die Funktion und aktualisiert doubled.

Wichtig: Die derived-Callback-Funktion bekommt die aktuellen Werte der Quell-Stores als Argument. Du arbeitest also nicht mit den Stores selbst, sondern mit ihren Werten.

Aus mehreren Stores

ts Mehrere Quellen
import { writable, derived } from 'svelte/store';

export const firstName = writable('Anna');
export const lastName = writable('Schmidt');

export const fullName = derived(
    [firstName, lastName],
    ([$firstName, $lastName]) => `${$firstName} ${$lastName}`
);

Bei mehreren Quellen wird ein Array übergeben. Die Callback-Funktion erhält ein Array der aktuellen Werte.

derived für asynchrone Werte

derived kann auch für asynchrone Berechnungen verwendet werden. Dafür gibt die Callback-Funktion nichts zurück, sondern bekommt eine set-Funktion als zweites Argument:

ts Async-Search
import { writable, derived } from 'svelte/store';

export const query = writable('');

export const results = derived(
    query,
    ($query, set) => {
        if (!$query) {
            set([]);
            return;
        }

        let cancelled = false;
        fetch(`/api/search?q=${$query}`)
            .then((r) => r.json())
            .then((data) => {
                if (!cancelled) set(data);
            });

        // Cleanup: läuft, bevor der nächste Run startet
        return () => { cancelled = true; };
    },
    [] // Initialwert
);

Schritt für Schritt:

  1. Wenn query sich ändert, läuft die Callback-Funktion.
  2. Die Cleanup-Funktion vom vorherigen Lauf wird vorher aufgerufen — wir setzen cancelled = true, um den alten Fetch zu „verwerfen”.
  3. Erst wenn das Ergebnis da ist und der Lauf nicht abgebrochen wurde, schreiben wir mit set(data).

Das ist ein robustes Pattern gegen veraltete Antworten („Race Conditions”) bei schnell wechselnden Eingaben.

Vergleichstabelle

HelferBeschreibungSchreibbar von außen?
writable(v)Store mit set, update, optionaler Setup-FunktionJa
readable(v)Store mit interner Setup-FunktionNein
derived(s)Aus anderen Stores abgeleitetNein

Faustregel:

  • „Wert kann sich von beliebiger Stelle ändern” -> writable.
  • „Wert ändert sich aus einer einzelnen Quelle” (Timer, externe Subscription) -> readable.
  • „Wert wird aus anderen Stores berechnet” -> derived.

TypeScript-Typen

Alle drei Stores sind generisch — der Typ wird vom Initialwert abgeleitet oder explizit angegeben:

ts Explizite Typen
import { writable, derived, type Writable, type Readable } from 'svelte/store';

// Inferred: Writable<number>
const count = writable(0);

// Explizit
const user: Writable<User | null> = writable(null);

// Read-only
const isLoggedIn: Readable<boolean> = derived(user, ($user) => $user !== null);

Die Helfer-Typen Writable<T> und Readable<T> aus svelte/store sind nützlich, wenn du Stores als API exportierst.

Häufige Stolperfallen

derived-Callback ohne Ableitung. Wenn du in derived Side Effects ausführst (Logging, API-Calls), die nichts mit dem Wert zu tun haben, ist derived das falsche Werkzeug. Dann eher subscribe(...) oder $effect.

derived mit veränderlichem Initialwert.

ts − Initialwert ist Referenz
const list = writable([]);
const filtered = derived(list, ($list) => $list, []); // gleiches Array

Bei async-derived mit Initialwert sicherstellen, dass der Initialwert nicht später mutiert wird.

readable ohne Cleanup. Wer setInterval startet, ohne ihn beim Stoppen zu räumen, hat einen Timer-Leak. Die Cleanup-Funktion in der Setup-Funktion zurückgeben.

writable ohne Initialwert. writable() ohne Argument hat den Wert undefined. Das ist meist Absicht — $store zeigt dann einfach nichts. In TypeScript musst du den Typ trotzdem als Writable<T | undefined> deklarieren.

derived synchron erwarten, asynchron schreiben. Bei async-derived wird der Wert über set aktualisiert. Wer aus der Callback-Funktion zurückgibt, überschreibt den synchronen Wert — und ignoriert das spätere set.

Weiterführende Ressourcen

Externe Quellen

Verwandte Artikel

/ Weiter

Zurück zu Stores

Zur Übersicht