Kurs Svelte i SvelteKit · Moduł 8: Stores
Stores vs $state
W tej lekcji6
W Svelte 5 stan współdzielony możesz trzymać w stores albo w runach. Kiedy wybrać które?
$state - Stan komponentu
<script>
// Lokalny stan komponentu
let count = $state(0);
let items = $state([]);
</script>Używaj gdy:
- Stan jest lokalny dla komponentu
- Nie potrzebujesz dostępu z innych komponentów
- Dane nie muszą przetrwać odmontowania
$state w pliku .svelte.js - stan współdzielony
Runy działają też poza komponentami, w plikach z końcówką .svelte.js (lub .svelte.ts). To zalecany w Svelte 5 sposób na stan współdzielony w nowym kodzie:
// treasury.svelte.js
export const treasury = $state({ gold: 0 });
export function addGold(amount) {
treasury.gold += amount;
}<!-- Chest.svelte -->
<script>
import { treasury, addGold } from './treasury.svelte.js';
</script>
<p>Złoto: {treasury.gold}</p>
<button onclick={() => addGold(10)}>+10 złota</button>Każdy komponent, który zaimportuje treasury, widzi to samo złoto - bez $ przed nazwą i bez subskrypcji. Eksportuj obiekt i zmieniaj jego pola: zmiennej $state, której przypisujesz nową wartość, nie wyeksportujesz wprost (kompilator zgłosi błąd).
W SvelteKit taki moduł jest jeden na cały serwer, więc dane konkretnego użytkownika przekazuj kontekstem (setContext, lekcja 4_5), a nie przez moduł.
Stores - nadal na miejscu
// stores.js
import { writable } from 'svelte/store';
export const user = writable(null);
export const cart = writable([]);Używaj gdy:
- Masz złożone, asynchroniczne strumienie danych (WebSocket, timery)
- Potrzebujesz ręcznej kontroli nad subskrypcją (start i stop w
readable) - Biblioteka albo starszy kod udostępnia store
Context + $state
<!-- Provider.svelte -->
<script>
import { setContext } from 'svelte';
let { children } = $props();
let state = $state({ user: null, theme: 'dark' });
setContext('app', {
get state() { return state; },
setUser: (user) => state.user = user,
toggleTheme: () => state.theme = state.theme === 'dark' ? 'light' : 'dark'
});
</script>
{@render children()}Porównanie
| Cecha | $state w komponencie | $state w .svelte.js | Stores |
|---|---|---|---|
| Zakres | Komponent | Cała aplikacja | Cała aplikacja |
| Odczyt w szablonie | {count} | {treasury.gold} | {$gold} |
| Dostęp poza komponentami | Nie | Tak | Tak (subscribe, get) |
| W nowym kodzie | Tak | Tak | Do strumieni i bibliotek |
Łączenie stores i run
Store możesz czytać z prefiksem $ także w runach, np. w $derived:
<script>
import { warriors } from './stores.js';
// $derived przelicza się, gdy zmieni się store
let activeWarriors = $derived($warriors.filter(w => w.active));
</script>
<p>Aktywni wojownicy: {activeWarriors.length}</p>Wartości wyliczane ze store zapisuj przez $derived, a nie $effect z przypisaniem - wynik jest zawsze aktualny i nie grozi pętla efektów.
Kod do tej lekcji: App.svelte
<script>
// Svelte 5: $state, $state w pliku .svelte.js i stores - kiedy co?
import { globalCount } from './stores.js';
import { shared } from './shared.svelte.js';
import Watcher from './Watcher.svelte';
// Lokalny state - tylko w tym komponencie
let localCount = $state(0);
</script>
<main class="comparison">
<h1>$state vs Stores</h1>
<section>
<h2>$state (lokalny)</h2>
<p>Wartość: <strong>{localCount}</strong></p>
<button onclick={() => localCount++}>+1 lokalnie</button>
<p class="hint">Tylko w tym komponencie</p>
</section>
<section>
<h2>$state w shared.svelte.js (współdzielony)</h2>
<p>Wartość: <strong>{shared.count}</strong></p>
<button onclick={() => shared.count++}>+1 współdzielony</button>
<p class="hint">Sposób Svelte 5 dla nowego kodu - bez $ i bez subskrypcji</p>
</section>
<section>
<h2>Store (współdzielony)</h2>
<p>Wartość: <strong>{$globalCount}</strong></p>
<button onclick={() => globalCount.update(n => n + 1)}>+1 w store</button>
<p class="hint">Nadal na miejscu: strumienie danych, biblioteki, starszy kod</p>
</section>
<Watcher />
<section class="guidelines">
<h2>Kiedy co używać?</h2>
<div class="guideline">
<h3>$state</h3>
<ul>
<li>Stan lokalny komponentu</li>
<li>Stan współdzielony w plikach .svelte.js</li>
<li>Formularze, stan UI (hover, open)</li>
</ul>
</div>
<div class="guideline">
<h3>Stores</h3>
<ul>
<li>Asynchroniczne strumienie danych</li>
<li>Ręczna kontrola subskrypcji</li>
<li>Biblioteki i starszy kod</li>
</ul>
</div>
</section>
</main>
<style>
.comparison {
font-family: 'Georgia', serif;
max-width: 600px;
margin: 0 auto;
padding: 2rem;
background: #1a0f0a;
color: #e8d5b7;
min-height: 100vh;
}
h1 {
color: #d4af37;
text-align: center;
}
section {
background: rgba(212, 175, 55, 0.1);
border: 1px solid #d4af37;
border-radius: 8px;
padding: 1rem;
margin: 1rem 0;
}
h2 {
color: #d4af37;
font-size: 1rem;
margin-top: 0;
}
strong {
color: #ffd700;
font-size: 1.5rem;
}
button {
padding: 0.5rem 1rem;
background: #3d2914;
color: #e8d5b7;
border: 1px solid #d4af37;
border-radius: 4px;
cursor: pointer;
}
.hint {
font-size: 0.85rem;
color: #8b7355;
font-style: italic;
margin-top: 0.5rem;
}
.guidelines {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.guidelines h2 {
grid-column: 1 / -1;
}
.guideline {
background: rgba(0,0,0,0.3);
padding: 1rem;
border-radius: 4px;
}
.guideline h3 {
color: #d4af37;
margin: 0 0 0.5rem 0;
font-size: 1rem;
}
.guideline ul {
margin: 0;
padding-left: 1.2rem;
}
.guideline li {
margin: 0.25rem 0;
font-size: 0.9rem;
}
</style>Hasła z dokumentacji w tej lekcji
Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Kiedy lepiej użyć Stores zamiast $state?