Vor Svelte 5 war die Antwort einfach: Geteilten State zwischen Komponenten = Store. Mit Runes hat sich das geändert. $state in einer .svelte.ts-Datei ist heute oft die einfachere und ergonomischere Wahl. Stores sind aber nicht weg — sie haben ihre Stärken weiterhin. Dieser Artikel hilft dir, in jeder Situation die richtige Wahl zu treffen, mit drei realen Szenarien als Vergleich.

Die kurze Antwort

SituationEmpfehlung
Geteilter Wert zwischen wenigen Komponenten in einer App$state
Lokaler State innerhalb einer Komponente$state
Modell mit Daten und Methoden (Klasse, Logik gebündelt)$state
Externe Bibliothek-API mit subscribe veröffentlichenStore
Asynchroner Stream / Datenquelle mit klarer Lifecycle-PhaseStore (readable mit Setup-Funktion)
Integration mit RxJS, Solid, externen FrameworksStore (Vertrag)
Persistente Werte (localStorage-Sync)Beides möglich, Custom Store oft kompakter

In neuen Svelte-5-Projekten wirst du deutlich häufiger $state sehen als Stores. Das ist Absicht — Runes sind ergonomischer und type-safer.

Szenario 1 – Globaler Counter

Klassisches Beispiel: Zwei Komponenten, ein gemeinsamer Counter.

Mit Store

ts src/lib/stores/counter.ts
import { writable } from 'svelte/store';

export const count = writable(0);
svelte Komponente A & B
<script>
    import { count } from '$lib/stores/counter';
</script>

<p>{$count}</p>
<button onclick={() => $count++}>+1</button>

Mit $state

ts src/lib/state/counter.svelte.ts
export const counter = $state({ value: 0 });
svelte Komponente A & B
<script>
    import { counter } from '$lib/state/counter.svelte';
</script>

<p>{counter.value}</p>
<button onclick={() => counter.value++}>+1</button>

Beide Lösungen funktionieren identisch. Die $state-Version ist etwas direkter — keine $-Syntax, kein Importieren aus svelte/store, der Wert ist ein normales Property.

Warum nicht direkt eine primitive Variable mit $state? Weil primitiv exportierte $state-Werte in TypeScript schlecht funktionieren — der reaktive „Trick” steckt im Proxy, und ein primitiver Wert hat keinen Proxy. Daher: in Objekt verpacken oder als Klasse modellieren.

Szenario 2 – Domänen-Modell mit Logik

Ein Cart, der Items, Total und Methoden zum Hinzufügen/Entfernen anbietet.

Mit Custom Store

ts src/lib/stores/cart.ts
import { writable, derived } from 'svelte/store';

function createCart() {
    const items = writable<CartItem[]>([]);

    return {
        items: { subscribe: items.subscribe },
        total: derived(items, ($items) =>
            $items.reduce((sum, i) => sum + i.price * i.qty, 0)
        ),
        add: (item: CartItem) =>
            items.update((cur) => [...cur, item]),
        remove: (id: string) =>
            items.update((cur) => cur.filter((i) => i.id !== id)),
        clear: () => items.set([]),
    };
}

export const cart = createCart();

Mit Klasse + $state

ts src/lib/state/cart.svelte.ts
type CartItem = { id: string; price: number; qty: number };

class Cart {
    items = $state<CartItem[]>([]);
    total = $derived(
        this.items.reduce((sum, i) => sum + i.price * i.qty, 0)
    );

    add(item: CartItem) {
        this.items.push(item);
    }

    remove(id: string) {
        this.items = this.items.filter((i) => i.id !== id);
    }

    clear() {
        this.items = [];
    }
}

export const cart = new Cart();
svelte Markup
<script>
    import { cart } from '$lib/state/cart.svelte';
</script>

<ul>
    {#each cart.items as item (item.id)}
        <li>{item.price * item.qty} €</li>
    {/each}
</ul>

<p>Gesamt: {cart.total.toFixed(2)} €</p>
<button onclick={cart.clear}>Leeren</button>

Die Klassen-Variante mit $state ist deutlich kompakter und nutzt natürliche Klassen-Methoden statt manueller update-Aufrufe. Der Compiler verfolgt automatisch, was reaktiv ist — mit voller Type-Inference.

Das ist der klare Sweetspot von $state: Domänen-Modelle mit Daten und Logik in einem Objekt.

Szenario 3 – Externe Datenquelle mit Subscription

Ein WebSocket-Stream oder eine Browser-API, die kontinuierlich neue Werte liefert.

Mit Store (passt natürlich)

ts src/lib/stores/online.ts
import { readable } from 'svelte/store';

export const isOnline = readable(navigator.onLine, (set) => {
    const handle = () => set(navigator.onLine);

    window.addEventListener('online', handle);
    window.addEventListener('offline', handle);

    return () => {
        window.removeEventListener('online', handle);
        window.removeEventListener('offline', handle);
    };
});

readable mit Setup/Cleanup ist hier genau das Richtige. Die Subscription wird automatisch aktiviert, wenn die erste Komponente abonniert, und beendet, wenn die letzte verschwindet.

Mit $state (möglich, aber umständlicher)

ts $state-Variante
import { browser } from '$app/environment';

export const isOnline = $state({ value: browser ? navigator.onLine : true });

if (browser) {
    const handle = () => (isOnline.value = navigator.onLine);
    window.addEventListener('online', handle);
    window.addEventListener('offline', handle);
    // Kein automatisches Cleanup — Listener läuft bis zum Page-Unload
}

Funktioniert — aber der Listener läuft bei jedem Modul-Import los und wird nie sauber entfernt. Für eine simple Online-Anzeige akzeptabel; für etwas Schwergewichtigeres wie WebSocket-Pings unschön. Hier ist readable mit der eingebauten Lifecycle-Funktion klar überlegen.

Was passiert, wenn ich beide kombiniere?

Du kannst sie mischen. Ein Store kann intern $state benutzen — und ein $state-Wert kann in einem derived-Store landen. Hier ist eine kompakte Brücke:

ts $state als Store ausgeben
import { readable, type Readable } from 'svelte/store';

function toStore<T>(getValue: () => T): Readable<T> {
    return {
        subscribe(callback) {
            let cleanup;
            cleanup = $effect.root(() => {
                $effect(() => {
                    callback(getValue());
                });
            });
            return cleanup;
        },
    };
}

export const counter = $state({ value: 0 });

// Als Store-API exportieren (z. B. für eine Library)
export const counterStore = toStore(() => counter.value);

In den meisten Apps brauchst du das nicht — aber wer eine Bibliothek schreibt, kann interne Runes-basierte Logik nach außen als Store anbieten und so beide Welten bedienen.

Was sich an Reaktivitäts-Verhalten unterscheidet

Ein technischer Aspekt, den man kennen sollte:

  • Stores liefern bei jeder Änderung den kompletten neuen Wert ans Subscribe — auch wenn nur eine kleine Property davon anders ist.
  • $state-Proxys verfolgen Zugriffe feingranular: Wenn ein Effect nur cart.total liest und cart.items mutiert wird, läuft der Effect nur, falls cart.total betroffen ist.

In großen Datenstrukturen ist das ein spürbarer Performance-Unterschied zugunsten von $state. Stores sind hier nicht „falsch”, aber bei tausenden Items und vielen Subscribern oft langsamer.

Praktische Empfehlung für neue Projekte

  • Default für geteilten State: $state in einer .svelte.ts-Datei, gerne in eine Klasse oder ein Objekt verpackt.
  • Wenn dieselbe Logik mehrfach nötig ist: $state-Klassen-Pattern, eine Klasse mit Methoden.
  • Für externe Datenquellen mit klarem Lifecycle: readable aus svelte/store.
  • Für externe Bibliotheks-API: Store-Vertrag (subscribe-Methode), damit Nicht-Svelte-Code damit umgehen kann.
  • Bestehender Code mit Stores: Funktioniert weiter — kein Grund zur Massen-Migration.

Häufige Stolperfallen

$state außerhalb von .svelte.ts exportieren. Geht nicht. $state darf nur in .svelte-, .svelte.ts- oder .svelte.js-Dateien stehen. Wer in normalem .ts arbeitet, muss zu Stores greifen.

$state mit primitivem Export.

ts − Funktioniert nicht wie erwartet
export let count = $state(0);

Beim Import in einer anderen Datei wird der aktuelle Wert kopiert — nicht der reaktive Container. Stattdessen Objekt oder Klasse exportieren.

Stores in Klassen mischen. Funktioniert, aber meistens unnötig. Wenn du eine Klasse hast, ist $state als Property die natürlichere Wahl.

Erwarten, dass Stores nach Migration zu Runes weiter funktionieren. Tun sie. Stores sind in Svelte 5 nicht entfernt. Aber neuer Code ist meistens schöner mit Runes.

Weiterführende Ressourcen

Externe Quellen

Verwandte Artikel

/ Weiter

Zurück zu Stores

Zur Übersicht