Gestion de l'État avec les Stores Svelte et SvelteKit
Dans le cadre du cours "Maîtriser SvelteKit : Développement Web Réactif et Ultra-Performant", cette leçon vous plongera au cœur de la gestion de l'état dans les applications Svelte et SvelteKit. Nous explorerons comment les stores Svelte, une fonctionnalité native et élégante, permettent de partager et de synchroniser des données à travers votre application de manière réactive et performante.
Introduction à la Gestion de l'État
Dans toute application web moderne, la gestion de l'état (ou state management) est un défi central. L'état représente toutes les données qui changent au fil du temps et qui doivent être partagées entre différentes parties de l'application. Cela peut inclure :
- Les données utilisateur (nom, identifiant de session)
- Les préférences de l'interface utilisateur (thème clair/sombre, langue)
- Les résultats d'appels API
- L'état d'un formulaire, etc.
Sans un mécanisme clair de gestion de l'état, les applications deviennent rapidement difficiles à maintenir. Les données se propagent via des "props" descendantes (prop drilling), ou des événements remontent la hiérarchie des composants, rendant le code spaghetti et sujet aux erreurs.
Svelte, fidèle à sa philosophie de simplicité et d'efficacité, offre une solution native et puissante pour la gestion de l'état global : les stores. Contrairement à d'autres frameworks qui nécessitent des bibliothèques tierces complexes (comme Redux pour React ou Vuex pour Vue), Svelte intègre cette fonctionnalité directement, avec une API minimaliste et intuitive.
Qu'est-ce qu'un Store Svelte ?
Un store Svelte est simplement un objet avec une méthode subscribe. C'est l'interface minimale requise pour qu'une variable Svelte puisse être réactive. En pratique, les stores vous permettent de créer des sources de données réactives qui peuvent être souscrites par n'importe quel composant dans votre application. Lorsqu'un store change, tous les composants qui y sont souscrits sont automatiquement mis à jour.
Les stores sont parfaits pour gérer :
- L'état global de l'application.
- L'état qui doit être partagé entre des composants non directement liés.
- Des données qui persistent au-delà de la durée de vie d'un composant individuel.
Les Types de Stores Svelte
Svelte propose trois types de stores principaux, chacun ayant ses propres cas d'utilisation :
1. writable Stores
Le store writable est le type de store le plus couramment utilisé. Il permet de stocker des valeurs qui peuvent être lues et modifiées.
- Création :
writable(initialValue) - Méthodes :
.set(newValue): Définit une nouvelle valeur pour le store, écrasant l'ancienne..update(updaterFunction): Met à jour la valeur du store en utilisant une fonction qui reçoit la valeur actuelle et retourne la nouvelle valeur. Utile pour des mises à jour basées sur l'état précédent.
2. readable Stores
Un store readable est un store en lecture seule. Sa valeur ne peut être définie qu'au moment de sa création ou par un callback interne au store lui-même. C'est utile pour des données qui proviennent d'une source externe et ne sont pas censées être modifiées par l'interface utilisateur (par exemple, l'heure actuelle, des données d'API statiques, etc.).
- Création :
readable(initialValue, startFunction)startFunctionest un callback optionnel appelé quand le premier abonné souscrit au store. Il doit retourner une fonctionstopqui sera appelée quand le dernier abonné se désabonne.
3. derived Stores
Un store derived (dérivé) permet de créer un nouveau store dont la valeur dépend d'un ou plusieurs autres stores. Lorsque les stores dont il dépend changent, le store dérivé est automatiquement recalculé et mis à jour. C'est idéal pour des données calculées ou agrégées.
- Création :
derived(stores, callbackFunction, initialValue)stores: Peut être un store unique ou un tableau de stores.callbackFunction: Une fonction qui reçoit la/les valeur(s) des stores dépendants et retourne la nouvelle valeur du store dérivé.initialValue(optionnel) : La valeur initiale du store dérivé avant que les dépendances ne soient prêtes.
Utilisation des Stores en Pratique
L'utilisation des stores Svelte est incroyablement simple grâce à leur réactivité intrinsèque et à la syntaxe concise de Svelte.
1. Le Store writable simple : un Compteur
Commençons par créer un store writable pour gérer un simple compteur.
Créez un fichier src/lib/stores/compteur.js (la convention src/lib/stores est courante pour organiser les stores) :
// src/lib/stores/compteur.js
import { writable } from 'svelte/store';
// Création d'un store writable avec une valeur initiale de 0
export const compteur = writable(0);
// Vous pouvez également exporter des fonctions pour modifier le store
// Cela centralise la logique de modification du store
export const increment = () => compteur.update(n => n + 1);
export const decrement = () => compteur.update(n => n - 1);
export const reset = () => compteur.set(0);
Maintenant, utilisons ce store dans un composant Svelte, par exemple src/routes/+page.svelte :
<!-- src/routes/+page.svelte -->
<script lang="ts">
import { compteur, increment, decrement, reset } from '$lib/stores/compteur';
// L'opérateur $ (dollarsign) est une fonctionnalité Svelte géniale !
// Il "déréférence" automatiquement le store et rend sa valeur réactive.
// Quand $compteur change, le DOM est mis à jour.
// Pas besoin de .subscribe() ou de .unsubscribe() manuels !
</script>
<h1>Compteur Svelte</h1>
<p>Valeur actuelle du compteur : <strong>{$compteur}</strong></p>
<button on:click={increment}>Incrémenter</button>
<button on:click={decrement}>Décrémenter</button>
<button on:click={reset}>Réinitialiser</button>
<style>
button {
margin: 5px;
padding: 10px 20px;
font-size: 1em;
cursor: pointer;
}
</style>
Explication du code :
- Nous importons le store
compteuret les fonctionsincrement,decrement,resetdepuis notre fichier de store. - L'élément clé est l'utilisation de
{$compteur}. Svelte reconnaît le préfixe$devant une variable et comprend qu'il s'agit d'un store. Il génère automatiquement le code JavaScript nécessaire pour souscrire au store au montage du composant et pour se désabonner à sa destruction. À chaque fois que la valeur du storecompteurchange (viasetouupdate), le texte affichant{$compteur}est automatiquement mis à jour. - Les boutons appellent les fonctions
increment,decrement,resetque nous avons exportées de notre fichier de store, ce qui modifie la valeur du storecompteur.
2. Le Store derived : État Calculé
Continuons avec l'exemple du compteur et créons un store dérivé qui nous dira si la valeur du compteur est paire ou impaire.
Mettez à jour src/lib/stores/compteur.js :
// src/lib/stores/compteur.js
import { writable, derived } from 'svelte/store';
export const compteur = writable(0);
export const increment = () => compteur.update(n => n + 1);
export const decrement = () => compteur.update(n => n - 1);
export const reset = () => compteur.set(0);
// Nouveau store dérivé
export const estPair = derived(
compteur, // Le store dont dépend 'estPair'
($compteur) => $compteur % 2 === 0 // La fonction qui calcule la nouvelle valeur
);
Maintenant, utilisez ce nouveau store estPair dans src/routes/+page.svelte :
<!-- src/routes/+page.svelte -->
<script lang="ts">
import { compteur, increment, decrement, reset, estPair } from '$lib/stores/compteur';
</script>
<h1>Compteur Svelte</h1>
<p>Valeur actuelle du compteur : <strong>{$compteur}</strong></p>
<p>Le nombre est : <strong>{$estPair ? 'Pair' : 'Impair'}</strong></p>
<button on:click={increment}>Incrémenter</button>
<button on:click={decrement}>Décrémenter</button>
<button on:click={reset}>Réinitialiser</button>
<style>
button {
margin: 5px;
padding: 10px 20px;
font-size: 1em;
cursor: pointer;
}
</style>
Explication du code :
- Nous avons créé
estPairen utilisantderived(compteur, ($compteur) => $compteur % 2 === 0). Cela signifie queestPairdépend decompteur. À chaque fois quecompteurchange, la fonction($compteur) => $compteur % 2 === 0est exécutée avec la nouvelle valeur decompteur, et le résultat devient la nouvelle valeur deestPair. - Dans le composant, nous utilisons
{$estPair}de la même manière que{$compteur}. Svelte gère la réactivité automatiquement.
3. Le Store readable : l'Heure Actuelle
Pour illustrer un store readable, créons un store qui diffuse l'heure actuelle chaque seconde.
Créez un fichier src/lib/stores/horloge.js :
// src/lib/stores/horloge.js
import { readable } from 'svelte/store';
// Création d'un store readable
export const horloge = readable(new Date(), function start(set) {
// Cette fonction est appelée quand le premier composant s'abonne
const interval = setInterval(() => {
set(new Date()); // Met à jour la valeur du store
}, 1000); // Toutes les secondes
// Cette fonction est retournée et appelée quand le dernier composant se désabonne
return function stop() {
clearInterval(interval);
};
});
Utilisez-le dans un composant, par exemple src/routes/horloge/+page.svelte :
<!-- src/routes/horloge/+page.svelte -->
<script lang="ts">
import { horloge } from '$lib/stores/horloge';
</script>
<h1>Heure Actuelle</h1>
<p>Il est actuellement : <strong>{$horloge.toLocaleTimeString()}</strong></p>
Explication du code :
- Le store
horlogeest créé avecreadable(new Date(), function start(set){...}). - La fonction
startest exécutée une seule fois lorsque le premier composant s'abonne àhorloge. Elle met en place unsetIntervalqui appelleset(new Date())chaque seconde, mettant à jour la valeur du store. - La fonction
stopest retournée parstart. Elle est appelée quand le dernier composant s'est désabonné dehorloge, ce qui permet de nettoyer la ressource (ici, lesetInterval) et d'éviter les fuites de mémoire.
Gestion de l'État dans SvelteKit
SvelteKit, étant construit sur Svelte, hérite naturellement de la capacité à utiliser les stores. Il y a quelques considérations spécifiques à SvelteKit :
1. Organisation des Stores
La convention de placer les stores dans src/lib/stores/ est une excellente pratique. src/lib est un alias par défaut dans SvelteKit qui pointe vers src/lib, facilitant les importations comme $lib/stores/compteur.js. Cela centralise votre logique de gestion d'état et la rend facilement importable n'importe où dans votre application.
2. Interaction avec les Layouts
Étant donné que les layouts (fichiers +layout.svelte) persistent à travers les navigations de pages et encapsulent l'interface utilisateur, ils sont des endroits idéaux pour injecter et consommer des stores globaux qui affectent l'ensemble de l'application (par exemple, un store d'authentification, un store de thème). Un store défini dans un layout peut être souscrit par n'importe quel composant enfant de ce layout.
3. Considérations pour le SSR (Server-Side Rendering)
SvelteKit gère le SSR, ce qui signifie que vos composants et vos stores peuvent être initialisés côté serveur avant d'être hydratés côté client.
- Initialisation : Lorsque vos stores sont importés et utilisés dans un composant qui est rendu sur le serveur, leur valeur initiale est celle définie dans le store.
- Réactivité : La réactivité des stores (c'est-à-dire les mises à jour et les abonnements) ne fonctionne que côté client (dans le navigateur). Sur le serveur, les stores sont simplement des objets avec leur valeur actuelle.
- États persistants vs. transitoires : Soyez conscient que les stores peuvent maintenir leur état entre les requêtes sur le serveur si vous ne les réinitialisez pas correctement ou si vous les importez dans un contexte global serveur. Pour les données spécifiques à une requête (
session,user), il est souvent préférable d'utiliser les fonctionsloadde SvelteKit (+page.server.jsou+layout.server.js) qui fournissent des données à la page ou au layout, plutôt que de tenter de gérer un état mutable global avec des stores Svelte standards côté serveur. Les stores Svelte sont principalement conçus pour la gestion de l'état client-side.
Bonnes Pratiques et Cas Avancés
- Convention du
$store: Adoptez systématiquement la syntaxe$storepour accéder aux valeurs des stores dans vos composants. C'est la manière idiomatique et la plus performante de travailler avec les stores Svelte, car elle gère automatiquement les abonnements et les désabonnements. - Séparation des préoccupations : Gardez la logique de vos stores (comment ils sont mis à jour, les validations, les appels API) séparée de la logique de présentation de vos composants. Exportez des fonctions qui encapsulent les opérations sur vos stores, comme nous l'avons fait avec
incrementetdecrement. - Stores personnalisés (Custom Stores) : Pour des cas d'utilisation plus complexes (par exemple, l'intégration avec le
localStorage, des websockets, des systèmes de caches), vous pouvez créer vos propres stores personnalisés en implémentant l'interface minimale (subscribeet éventuellementset/updatesi c'est un store modifiable). - Stores vs. Context API :
- Les stores sont idéaux pour un état global qui doit être accessible de n'importe où dans l'arbre des composants, ou même en dehors des composants (dans des modules utilitaires).
- L'API Context de Svelte est meilleure pour partager des données avec un sous-arbre spécifique de composants, évitant le "prop drilling" sans rendre l'état global. C'est un peu comme un "mini-store" scoped à une partie de l'application.
Conclusion
Les stores Svelte représentent une solution élégante, simple et incroyablement puissante pour la gestion de l'état dans vos applications. Leur intégration native dans le framework, combinée à la syntaxe concise de Svelte (notamment l'opérateur $), élimine la complexité souvent associée à la gestion de l'état dans d'autres écosystèmes.
En maîtrisant les stores writable, readable et derived, vous êtes bien équipé pour construire des applications Svelte et SvelteKit réactives, maintenables et ultra-performantes, capables de gérer n'importe quel type de données partagées avec aisance. Vous avez maintenant les outils pour centraliser et coordonner l'état de votre application de manière fluide et intuitive.