Zustand : Guide Complet de Gestion d'État pour React
Découvrez Zustand, la bibliothèque de gestion d'état légère et performante pour React. Apprenez à l'utiliser, à optimiser les rendus et à gérer le state persistant avec Immer et Persist.
Introduction
Zustand est une bibliothèque de gestion d'état légère et rapide, idéale pour construire des applications React, des compteurs simples aux jeux de cartes complexes directement dans le navigateur.
Précis de configuration
| Élément | Version / Lien |
|---|---|
| Langage / Runtime | JavaScript / TypeScript, Node.js |
| Librairie principale | Zustand |
| APIs requises | create (de 'zustand'), shallow (de 'zustand/shallow'), persist (de 'zustand/middleware'), immer (de 'zustand/middleware/immer') |
| Clés / credentials nécessaires | Aucune |
Guide étape par étape
Étape 1 — Installation de Zustand
Pour commencer à utiliser Zustand dans votre projet, vous devez l'installer via npm ou yarn. Cette commande ajoute la bibliothèque à vos dépendances.
npm install zustand
Étape 2 — Création d'un Store Zustand de base
Un store Zustand est créé à l'aide de la fonction create. Il s'agit d'un hook personnalisé qui encapsule votre état et vos actions. Pour une meilleure typisation, il est recommandé de définir une interface pour votre état.
// src/state/useCounterStore.ts
import { create } from "zustand";
type CounterState = {
count: number;
increment: () => void;
decrement: () => void;
reset: () => void;
};
export const useCounterStore = create<CounterState>((set) => ({
count: 0, // État initial du compteur
increment: () => set((state) => ({ count: state.count + 1 })), // Action pour incrémenter
decrement: () => set((state) => ({ count: state.count - 1 })), // Action pour décrémenter
reset: () => set(() => ({ count: 0 })), // Action pour réinitialiser
}));
Étape 3 — Consommation du Store dans les Composants React
Pour utiliser l'état et les actions de votre store Zustand dans un composant React, vous appelez simplement le hook useCounterStore et sélectionnez les parties de l'état dont vous avez besoin. Zustand gère automatiquement les re-rendus de manière optimisée.
// src/components/Counter.tsx
import { useCounterStore } from "../state/useCounterStore";
import { shallow } from 'zustand/shallow'; // Importez shallow pour l'optimisation
// ... (autres imports et hooks comme useFlash si nécessaire)
export function Counter() {
return (
<>
<CountDisplay />
<CountControls />
</>
);
}
function CountDisplay() {
// Sélectionne uniquement 'count' pour que ce composant ne se rende que si 'count' change
const count = useCounterStore((state) => state.count);
// ... (autres hooks et rendu du display)
return (
<div className="card">
<p className="count-display">{count}</p>
<p className="render-count">Renders: {renders}</p>
</div>
);
}
function CountControls() {
// Sélectionne les actions et utilise 'shallow' pour éviter les re-rendus inutiles
const { increment, decrement, reset } = useCounterStore(
(state) => ({
increment: state.increment,
decrement: state.decrement,
reset: state.reset,
}),
shallow // Utilise shallow pour une comparaison superficielle de l'objet retourné
);
// ... (autres hooks et rendu des contrôles)
return (
<div className="card">
<div className="button-group">
<button onClick={decrement}>-1</button>
<button onClick={reset}>Reset</button>
<button onClick={increment}>+1</button>
<button onClick={incrementOutsideReact}>+1 Outside</button>
</div>
<p className="render-count">Renders: {renders}</p>
</div>
);
}
// Fonction pour incrémenter le compteur en dehors d'un composant React
function incrementOutsideReact() {
console.log("Outside of React");
useCounterStore.getState().increment(); // Accès direct à l'état et aux actions du store
}
Étape 4 — Accès au Store en dehors des Composants React
Zustand permet d'accéder à l'état et aux actions du store directement, sans être dans un composant React. C'est utile pour les logiques métier ou les interactions avec des APIs externes.
// Exemple d'utilisation en dehors d'un composant React
// Pour obtenir l'état actuel :
const currentCount = useCounterStore.getState().count;
console.log("Current count outside React:", currentCount);
// Pour mettre à jour l'état :
useCounterStore.setState({ count: 10 }); // Définit le compteur à 10
// Pour appeler une action :
useCounterStore.getState().increment(); // Incrémente le compteur
Étape 5 — Structuration des Actions dans un sous-objet
Pour une meilleure organisation, il est courant de regrouper les actions dans un sous-objet actions au sein de votre CounterState. Cela clarifie la distinction entre l'état et les fonctions qui le modifient.
// src/state/useCounterStore.ts
// ... (imports)
type CounterActions = {
increment: () => void;
decrement: () => void;
reset: () => void;
};
type CounterState = {
count: number;
actions: CounterActions; // Les actions sont maintenant dans un sous-objet
};
export const useCounterStore = create<CounterState>((set) => ({
count: 0,
actions: { // Définition des actions dans le sous-objet
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set(() => ({ count: 0 })),
},
}));
// Dans les composants, vous accédez aux actions via state.actions
// Exemple pour CountControls :
// const { actions } = useCounterStore((state) => ({ actions: state.actions }), shallow);
// <button onClick={actions.increment}>+1</button>
// Exemple pour incrementOutsideReact :
// useCounterStore.getState().actions.increment();
Étape 6 — Persistance de l'état avec persist

Le middleware persist de Zustand permet de sauvegarder automatiquement l'état de votre store dans le stockage local (ou autre) et de le recharger lors de l'initialisation. Cela assure que l'état de l'utilisateur est conservé entre les sessions.
// src/state/useCounterStore.ts
import { create } from "zustand";
import { persist } from "zustand/middleware"; // Importez le middleware persist
// ... (types CounterActions et CounterState)
export const useCounterStore = create<CounterState>(
persist(
(set) => ({
count: 0,
actions: {
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set(() => ({ count: 0 })),
},
}),
{ name: "count" } // Options de persistance : 'name' est la clé dans le stockage local
)
);
Étape 7 — Gestion de l'état imbriqué avec immer

Lorsque votre état devient complexe et imbriqué, la mise à jour immuable peut devenir fastidieuse. Le middleware immer permet de muter directement un brouillon de l'état, et Immer s'occupe de produire un nouvel état immuable en arrière-plan, simplifiant grandement le code.
// src/state/useCounterStore.ts
import { create } from "zustand";
import { persist } from "zustand/middleware";
import { immer } from "zustand/middleware/immer"; // Importez le middleware immer
// ... (types CounterActions et CounterState)
type UserAddress = {
street: string;
zipcode: string;
};
type User = {
name: string;
address: UserAddress;
};
type CounterState = {
count: number;
user: User; // Ajout d'un état utilisateur imbriqué
actions: CounterActions;
};
export const useCounterStore = create<CounterState>(
immer(
persist(
(set) => ({
count: 0,
user: { // État initial de l'utilisateur
name: "Kyle",
address: {
street: "Main St",
zipcode: "23423",
},
},
actions: {
increment: () => set((state) => { state.count++; }), // Mutation directe grâce à Immer
decrement: () => set((state) => { state.count--; }),
reset: () => set((state) => { state.count = 0; }),
updateStreet: (newStreet: string) => set((state) => { // Mise à jour imbriquée simplifiée
state.user.address.street = newStreet;
}),
},
}),
{ name: "count" } // Options de persistance
)
)
);
Tableaux comparatifs
| Caractéristique | React Context | Zustand |
|---|---|---|
| Re-rendus | Tous les composants consommateurs re-rendent sur tout changement de contexte. | Seuls les composants sélectionnant la partie modifiée de l'état re-rendent. |
| Scalabilité | Difficile à scaler pour les grandes applications avec des états globaux complexes. | Très scalable, conçu pour les applications de toute taille. |
| API | Basée sur useContext et useReducer (souvent). |
Basée sur un hook create simple, avec des middlewares optionnels. |
| Accès hors React | Nécessite de passer les fonctions via props ou de recréer le contexte. | Accès direct à l'état et aux actions via useStore.getState() et useStore.setState(). |
| Boilerplate | Peut être verbeux avec les Provider et Consumer. |
Minimaliste, moins de boilerplate. |
| Optimisation | Nécessite React.memo ou useCallback pour optimiser les re-rendus. |
Optimisation des re-rendus intégrée via la sélection de l'état et shallow. |
⚠️ Erreurs fréquentes et pièges
Re-rendus excessifs avec la sélection d'objets : Si vous sélectionnez un objet directement (ex:
const { actions } = useCounterStore((state) => ({ actions: state.actions }));), React recréera une nouvelle référence d'objet à chaque rendu, provoquant des re-rendus inutiles.
Solution : Utilisez le comparateurshallowde Zustand :const { actions } = useCounterStore((state) => ({ actions: state.actions }), shallow);Mise à jour immuable manuelle de l'état imbriqué : Sans
immer, la mise à jour d'objets profondément imbriqués nécessite de copier manuellement chaque niveau de l'objet, ce qui est source d'erreurs et de verbosité.
Solution : Intégrez le middlewareimmerpour permettre des mutations directes et simplifiées de l'état.Oubli de la typisation avec TypeScript : Travailler avec Zustand et TypeScript sans définir correctement les types peut entraîner des erreurs de compilation et une perte des avantages de la typisation.
Solution : Définissez toujours des interfaces claires (CounterState,CounterActions) et utilisez-les avec la fonctioncreate<YourState>.
Glossaire
Store : Conteneur centralisé pour l'état global d'une application, accessible par tous les composants qui en ont besoin.
Middleware : Fonction qui s'intercale entre l'action et la mise à jour de l'état, permettant d'ajouter des fonctionnalités comme la persistance ou la journalisation.
Shallow Equality : Comparaison superficielle entre deux objets, vérifiant si leurs propriétés de premier niveau sont identiques en référence, sans inspecter les objets imbriqués.
Points clés à retenir
- Zustand est une alternative légère et performante à React Context pour la gestion d'état.
- Il permet une sélection granulaire de l'état, minimisant les re-rendus des composants.
- Le hook
useShallowest essentiel pour optimiser les sélections d'objets et éviter les re-rendus inutiles. - L'état et les actions du store peuvent être accédés en dehors des composants React, offrant une grande flexibilité.
- Les middlewares comme
persistetimmerétendent les fonctionnalités du store pour la persistance et la gestion simplifiée de l'état immuable. - La typisation avec TypeScript est fortement recommandée pour maintenir la robustesse du code.