Cours Svelte et SvelteKit · Module 8: Stores
Stores personnalisés
Dans cette leçon3
Crée tes propres stores avec une logique métier :
Exemple : Store de compteur
// counterStore.js
import { writable } from 'svelte/store';
function createCounter(initial = 0) {
const { subscribe, set, update } = writable(initial);
return {
subscribe,
increment: () => update(n => n + 1),
decrement: () => update(n => n - 1),
reset: () => set(initial),
set
};
}
export const counter = createCounter(0);<script>
import { counter } from './counterStore.js';
</script>
<p>Count: {$counter}</p>
<button onclick={counter.increment}>+</button>
<button onclick={counter.decrement}>-</button>
<button onclick={counter.reset}>Reset</button>Exemple : Store de guerriers
// warriorsStore.js
import { writable, derived } from 'svelte/store';
function createWarriorsStore() {
const { subscribe, set, update } = writable([]);
return {
subscribe,
add: (warrior) => update(warriors => [...warriors, {
id: Date.now(),
...warrior
}]),
remove: (id) => update(warriors =>
warriors.filter(w => w.id !== id)
),
updateStrength: (id, strength) => update(warriors =>
warriors.map(w => w.id === id ? { ...w, strength } : w)
),
reset: () => set([])
};
}
export const warriors = createWarriorsStore();
// Stores dérivés
export const totalWarriors = derived(warriors, $w => $w.length);
export const totalStrength = derived(warriors,
$w => $w.reduce((sum, w) => sum + w.strength, 0)
);Store avec localStorage
// settingsStore.js
import { writable } from 'svelte/store';
function createPersistentStore(key, initial) {
// SSR (SvelteKit) : il n'y a pas de localStorage sur le serveur
const storage = typeof localStorage !== 'undefined' ? localStorage : null;
const stored = storage?.getItem(key);
const data = stored ? JSON.parse(stored) : initial;
const { subscribe, set, update } = writable(data);
return {
subscribe,
set: (value) => {
storage?.setItem(key, JSON.stringify(value));
set(value);
},
update: (fn) => {
update(current => {
const newValue = fn(current);
storage?.setItem(key, JSON.stringify(newValue));
return newValue;
});
}
};
}
export const settings = createPersistentStore('settings', {
theme: 'dark',
language: 'pl'
});La vérification typeof localStorage évite une erreur du module pendant le rendu serveur dans SvelteKit : le store y garde sa valeur initiale.
Code de cette leçon : App.svelte
<script>
// Store personnalisé avec méthodes
import { warriors, addWarrior, removeWarrior, stats } from './stores.js';
let newName = $state('');
function handleAdd() {
if (newName.trim()) {
addWarrior(newName);
newName = '';
}
}
</script>
<main class="warriors-app">
<h1>Store personnalisé</h1>
<p class="subtitle">Store avec méthodes personnalisées</p>
<section>
<h2>Ajouter un guerrier</h2>
<div class="add-form">
<input
bind:value={newName}
placeholder="Nom du guerrier"
onkeydown={(e) => e.key === 'Enter' && handleAdd()}
/>
<button onclick={handleAdd}>Ajouter</button>
</div>
</section>
<section>
<h2>Liste des guerriers ({$warriors.length})</h2>
{#each $warriors as warrior (warrior.id)}
<div class="warrior-row">
<span>{warrior.name}</span>
<span class="health">{warrior.health}</span>
<button onclick={() => removeWarrior(warrior.id)} aria-label="Retirer {warrior.name}">×</button>
</div>
{:else}
<p class="empty">Aucun guerrier</p>
{/each}
</section>
<section class="stats">
<h2>Statistiques (derived)</h2>
<div class="stat-grid">
<div class="stat">
<span>Total</span>
<strong>{$stats.total}</strong>
</div>
<div class="stat">
<span>PV total</span>
<strong>{$stats.totalHealth}</strong>
</div>
<div class="stat">
<span>PV moyens</span>
<strong>{$stats.avgHealth}</strong>
</div>
</div>
</section>
</main>
<style>
.warriors-app {
font-family: 'Georgia', serif;
max-width: 500px;
margin: 0 auto;
padding: 2rem;
background: #1a0f0a;
color: #e8d5b7;
min-height: 100vh;
}
h1 {
color: #d4af37;
text-align: center;
}
.subtitle {
text-align: center;
color: #8b7355;
}
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;
}
.add-form {
display: flex;
gap: 0.5rem;
}
input {
flex: 1;
padding: 0.5rem;
background: rgba(0,0,0,0.3);
border: 1px solid #3d2914;
border-radius: 4px;
color: #e8d5b7;
}
button {
padding: 0.5rem 1rem;
background: #3d2914;
color: #e8d5b7;
border: 1px solid #d4af37;
border-radius: 4px;
cursor: pointer;
}
.warrior-row {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
background: rgba(0,0,0,0.3);
margin: 0.25rem 0;
border-radius: 4px;
}
.warrior-row span:first-child {
flex: 1;
}
.health {
color: #ff6b6b;
}
.warrior-row button {
background: #8b0000;
border-color: #8b0000;
padding: 0.25rem 0.5rem;
}
.empty {
text-align: center;
color: #8b7355;
}
.stat-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.5rem;
}
.stat {
text-align: center;
padding: 0.5rem;
background: rgba(0,0,0,0.3);
border-radius: 4px;
}
.stat span {
display: block;
font-size: 0.85rem;
color: #8b7355;
}
.stat strong {
font-size: 1.25rem;
color: #d4af37;
}
</style>Entrées de la documentation utilisées dans cette leçon
Tu as repéré une erreur dans cette leçon ?
Teste tes connaissances
Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.
1. Que doit exporter un custom store pour être compatible avec la syntaxe $ ?