Wenn ein Wert in mehreren Komponenten gebraucht wird, gibt es in Svelte drei Mechanismen: Props für direkte Eltern-Kind-Beziehungen, Context für hierarchische Vererbung im Komponenten-Baum, Stores (oder ein globaler $state-Container) für App-weite Werte. Sie sind nicht austauschbar — jeder hat eine Sweet Spot. Dieser Artikel hilft, sich klar zu entscheiden.
Die drei Mechanismen kurz im Überblick
| Mechanismus | Reichweite | Wer setzt? | Wer liest? |
|---|---|---|---|
| Props | Genau ein Eltern → ein direktes Kind | Eltern beim Aufruf | Kind via $props() |
| Context | Eine Komponente → alle ihre Nachfahren | Eltern via setContext | Beliebige Nachfahren via getContext |
Store / globaler $state | App-weit, beliebige Stellen | Wer den Wert importiert | Wer den Wert importiert |
Faustregel zum Einprägen: Props sind der Standard. Context ist für Werte, die in tiefen Bäumen gebraucht werden. Stores oder globaler $state sind für Werte, die nichts mit der Komponenten-Hierarchie zu tun haben.
Beispiel-Szenario: Theme-Wechsel
Stell dir vor, deine App hat einen Theme-Toggle und alle Komponenten sollen sich entsprechend einfärben. Wie löst du das?
Variante 1 – Mit Props
<Layout {theme}>
<Sidebar {theme}>
<SidebarItem {theme}>
…
</SidebarItem>
</Sidebar>
</Layout>Funktioniert, ist aber Prop-Drilling pur. Jede Zwischenkomponente muss theme durchreichen, auch wenn sie nichts damit anfängt. In einer großen App wird das schnell unhaltbar.
Variante 2 – Mit Context
<script>
import { provideTheme } from '$lib/contexts/theme.svelte';
const theme = provideTheme();
</script>
<Layout>
<Sidebar>
<SidebarItem />
</Sidebar>
</Layout>Layout, Sidebar und SidebarItem müssen theme nicht kennen. Nur die Komponenten, die das Theme tatsächlich brauchen, holen es per useTheme() aus dem Context. Sauberer Datenfluss, klar markierte Konsumenten.
Variante 3 – Mit globalem $state
export const theme = $state({ value: 'light' as 'light' | 'dark' });<script>
import { theme } from '$lib/state/theme.svelte';
</script>
<div class={`box theme-${theme.value}`}>…</div>Auch das funktioniert. Die Komponente muss noch nicht mal in einem bestimmten Baum stehen — sie importiert den State direkt aus dem Modul.
Welche Variante ist richtig?
Für einen App-weiten Theme-Toggle wären sowohl Variante 2 (Context) als auch Variante 3 (globaler State) vertretbar. Variante 2 ist sauberer, wenn du theoretisch mehrere Themes parallel haben willst (z. B. ein Modal, das immer light bleibt) — weil Context hierarchisch ist, kann ein Sub-Baum einen anderen Wert haben. Variante 3 ist kompakter, wenn das Theme tatsächlich global ist und nie pro Bereich anders.
Variante 1 (Props) ist hier eindeutig die schlechteste Wahl — der Wert wird auf der Wurzel gesetzt und an Blättern gelesen, dazwischen ist er nur Ballast.
Gegenbeispiel: Wann Props gewinnen
Stell dir eine Listen-Komponente vor, die ihre Items rendert. Jedes Item bekommt sein Datum als Prop. Würde man das per Context lösen?
{#each items as item (item.id)}
<ListItem>
{#snippet …}
<!-- Item bezieht sich auf einen Context, den die List-Komponente
pro Iteration neu setzen müsste — das ist nicht möglich -->
{/snippet}
</ListItem>
{/each}Funktioniert nicht sauber: Context wird einmal pro Komponenten-Setup gesetzt, nicht pro Iteration. Hier ist die Prop die einzig richtige Lösung:
{#each items as item (item.id)}
<ListItem {item} />
{/each}Pro Item wird die Komponente mit ihrem eigenen item-Wert aufgerufen. Direkt, lesbar, kein Trick.
Wann Stores statt Context?
Beide können „über Komponenten hinweg geteilte Werte” abbilden. Der Unterschied liegt im Gültigkeitsbereich:
- Context ist an die Komponenten-Hierarchie gebunden. Eine Komponente außerhalb des Baums sieht den Wert nicht. Tests, die eine Komponente isoliert mounten, müssen den Context manuell nachliefern.
- Stores und globale
$state-Container sind app-weit. Jede Datei kann sie importieren, jeder Test bekommt automatisch den selben Wert (und ggf. dessen Verschmutzung aus vorherigen Tests).
Daraus folgen zwei Faustregeln:
- Context für Werte, die zu einer Komponenten-Instanz gehören oder zu einem Sub-Baum. Beispiel: Eine
<Form>und ihre<Field>-Kinder, eine<Tabs>und ihre<TabPanel>-Kinder. - Store / globaler
$statefür Werte, die zu einer App-Instanz gehören. Beispiel: Auth-Status, Spracheinstellung, Toast-Manager.
Das Theme-Beispiel von oben ist ein Grenzfall — beides ist defensible. In der Praxis wird ein Theme-Wechsel oft per globalem State gelöst, weil er ohnehin nur einmal pro App existiert.
Entscheidungsbaum
Beim Verteilen eines Wertes diese Fragen der Reihe nach:
| Frage | Wenn ja → nimm |
|---|---|
| Wird der Wert nur ein Komponenten-Level tief gebraucht? | Prop |
| Wird er in einem klar abgegrenzten Sub-Baum gebraucht (Form, Tabs)? | Context |
| Wird er von vielen voneinander unabhängigen Komponenten gebraucht und ist app-weit eindeutig? | Globaler $state / Store |
| Soll er pro Sub-Baum unterschiedlich sein (geschachtelte Themes etc.)? | Context (mehrfach gesetzt) |
Wenn du dich nicht entscheiden kannst, ist Props meist der sicherere Default. Context und globalen State führt man bewusst ein, sobald Prop-Drilling spürbar lästig wird.
Patterns aus der Praxis
Auth-Status → globaler $state in .svelte.ts. Auth ist app-weit eindeutig, jede Komponente kann ihn unabhängig vom Baum konsumieren.
Theme → meist globaler $state. Wenn geschachtelte Themes nötig sind, dann Context.
Form-Validierung → Context. Eine <Form>-Komponente und ihre <Field>-Kinder gehören klar zusammen, mehrere Forms in der App sollen sich nicht stören.
Toast/Snackbar-Manager → globaler $state plus Custom DOM Event. Jede Komponente kann einen Toast auslösen, der Container hört zentral darauf.
Theme der aktuellen Modal-Schicht → Context. Hier gewinnt die hierarchische Variante deutlich, weil ein Modal eigene Theme-Regeln haben kann.
Locale / Sprache → globaler $state. App-weit identisch, nur ein Wert relevant.
API-Client (HTTP-Instance) → Context, falls eine Library die API-Instanz pro Komponente unterschiedlich konfigurieren können soll. Sonst globaler State.
Häufige Stolperfallen
Context als Ersatz für globalen State.
Wenn jede Komponente in der App den Wert braucht, ist ein globaler $state direkter — kein setContext-Aufruf in einer Wurzel-Komponente nötig, kein Test-Setup.
Globaler State als Ersatz für Props.
Wenn nur eine Komponente den Wert braucht und einmal ändert, ist eine Prop oder lokaler $state klarer als ein App-weites Modul.
Context-Wert wird in einer Geschwister-Komponente erwartet. Geschwister sehen den Context nicht. Der Context muss bei einem gemeinsamen Vorfahren gesetzt werden.
Globalen State zwischen mehreren App-Instanzen oder SSR-Requests teilen.
Auf dem Server (SvelteKit) ist globaler State gefährlich: Mehrere Requests teilen sich denselben Modul-Speicher, was zu Cross-Request-Datenleaks führen kann. Auth-Status, Locale und ähnliches gehören dort in request-spezifischen Kontext (z. B. event.locals plus Context).
Tests laufen nicht, weil Context fehlt.
Wenn du eine einzelne Komponente isoliert testest, hat sie keinen Eltern-Komponenten, der setContext aufruft. Lösung: Testumgebung mit einem Test-Wrapper-Komponent setzen, der den Context vorbefüllt.