Kurs Svelte i SvelteKit · Moduł 8: Stores

Stores vs $state

3 min czytania ·
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.jsStores
ZakresKomponentCała aplikacjaCała aplikacja
Odczyt w szablonie{count}{treasury.gold}{$gold}
Dostęp poza komponentamiNieTakTak (subscribe, get)
W nowym kodzieTakTakDo 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. 1. Kiedy lepiej użyć Stores zamiast $state?

Przydatne artykuły