Zustand et Jotai : Gestion d'État Légère et Performante
Bienvenue dans cette leçon consacrée à deux bibliothèques de gestion d'état qui redéfinissent la simplicité et la performance dans l'écosystème des Single Page Applications (SPAs) modernes : Zustand et Jotai. Dans le cadre de notre parcours pour "Maîtriser la Gestion d'État Avancée : Architectures Robustes pour SPAs Modernes", nous allons explorer comment ces solutions légères et élégantes peuvent vous aider à construire des applications plus rapides, plus maintenables et moins sujettes au boilerplate.
La gestion d'état est le cœur battant de toute application interactive. Traditionnellement, des outils robustes comme Redux ont dominé, apportant structure et prévisibilité. Cependant, l'évolution rapide de React et l'émergence de nouvelles philosophies ont ouvert la voie à des alternatives qui se veulent plus minimalistes, plus directes et souvent plus performantes pour de nombreux cas d'usage. Zustand et Jotai incarnent parfaitement cette nouvelle vague, en tirant parti des Hooks de React pour offrir une expérience développeur optimisée sans sacrifier la puissance nécessaire aux applications complexes.
Dans cette leçon, nous allons décortiquer leurs principes fondamentaux, explorer leur API à travers des exemples concrets, et discuter de leurs avantages, de leurs cas d'usage idéaux, ainsi que des considérations pour choisir la bonne solution pour vos projets.
1. Comprendre les Défis de la Gestion d'État Moderne
Avant de plonger dans Zustand et Jotai, il est crucial de se rappeler pourquoi la gestion d'état reste un défi central et pourquoi de nouvelles solutions sont toujours pertinentes.
Les Problématiques Courantes :
- Boilerplate (Code Répétitif) : De nombreuses solutions historiques exigent une quantité significative de code pour des actions simples (définir des actions, des reducers, des sélecteurs, connecter des composants...). Cela peut alourdir le développement et la lecture du code.
- Performance des Re-Renders : Une gestion d'état inefficace peut entraîner des re-renders inutiles de composants, affectant la performance de l'application, surtout à mesure qu'elle grandit.
- Complexité et Courbe d'Apprentissage : Certains frameworks de gestion d'état ont une courbe d'apprentissage abrupte et introduisent de nombreux concepts abstraits, ce qui peut freiner l'intégration de nouveaux développeurs ou la rapidité de développement.
- Maintenabilité : Une architecture d'état mal conçue peut rendre l'application difficile à comprendre, à débugger et à faire évoluer.
- Flexibilité : La capacité à gérer à la fois l'état global et l'état local partagé, ainsi que les états asynchrones et dérivés, de manière cohérente et performante.
Des solutions comme le Context API de React ont apporté de la simplicité pour l'injection de dépendances et le partage d'état, mais elles peuvent souffrir de problèmes de performance si l'état est mis à jour fréquemment et que de nombreux composants s'y abonnent sans optimisation. C'est dans ce contexte que Zustand et Jotai proposent des approches innovantes pour relever ces défis.
2. Zustand : Le Store Sans Boilerplate
Zustand (mot allemand signifiant "état") est une petite bibliothèque de gestion d'état pour React, inspirée de Flux/Redux mais avec une approche beaucoup plus minimaliste et "Hook-friendly". Son objectif est de fournir une solution légère, rapide et évolutive sans les surcouches typiques.
Principes Clés de Zustand :
- Pas de Context Provider : Contrairement au Context API ou à Redux, Zustand ne nécessite pas d'envelopper votre application avec un
Provider. Le store est globalement accessible et fonctionne indépendamment de l'arbre de composants. - API Basée sur les Hooks : Le store est créé avec une simple fonction
createet interagi avec des Hooks (useStore). - Mises à Jour Immuables Simplifiées : Les fonctions de mise à jour du store reçoivent l'état précédent et retournent le nouvel état, encourageant naturellement les mises à jour immuables sans la complexité des reducers de Redux.
- Performances Granulaires : Zustand gère les abonnements de manière fine. Seuls les composants qui utilisent une partie spécifique de l'état du store sont re-rendus lorsque cette partie change.
- Convivialité pour TypeScript : Très bien intégré avec TypeScript pour une meilleure autocomplétion et une sécurité de type.
Mise en Pratique avec Zustand
Découvrons Zustand à travers un exemple simple de compteur.
2.1. Installation
npm install zustand
# ou
yarn add zustand
2.2. Création d'un Store Simple
Un store Zustand est une fonction créée avec create qui prend un set comme argument pour modifier l'état, et retourne l'état initial et les actions.
// stores/useCounterStore.js
import { create } from 'zustand';
// Définition du store du compteur
const useCounterStore = create((set) => ({
count: 0, // État initial
// Actions pour modifier l'état
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}));
export default useCounterStore;
Explication :
create((set) => ({...})): C'est la fonction principale de Zustand pour définir un store. Elle prend une fonction en argument qui reçoitset(une fonction pour mettre à jour l'état) etget(une fonction pour obtenir l'état actuel) si nécessaire.count: 0: C'est l'état initial de notre compteur.increment,decrement,reset: Ce sont les actions qui modifient l'état. Elles utilisent la fonctionsetpour retourner un nouvel objet d'état, ce qui déclenche un re-render des composants abonnés. Notez l'utilisation de(state) => ({ count: state.count + 1 })pour accéder à l'état précédent de manière sécurisée et immuable.
2.3. Utilisation du Store dans un Composant React
Pour utiliser le store dans un composant, vous appelez le Hook que create vous a retourné et lui passez un sélecteur pour extraire les parties de l'état dont vous avez besoin.
// components/CounterZustand.jsx
import React from 'react';
import useCounterStore from '../stores/useCounterStore'; // Importez votre store
function CounterZustand() {
// Extrait la valeur de 'count' et les fonctions d'action du store
// Zustand optimise les re-renders : seul le composant sera re-rendu si 'count' ou les fonctions d'action changent.
// Les fonctions d'action sont stables et ne causeront pas de re-render inutiles.
const count = useCounterStore((state) => state.count);
const increment = useCounterStore((state) => state.increment);
const decrement = useCounterStore((state) => state.decrement);
const reset = useCounterStore((state) => state.reset);
return (
<div style={{ border: '1px solid #ccc', padding: '15px', borderRadius: '5px', marginBottom: '20px' }}>
<h3>Compteur avec Zustand</h3>
<p>Valeur actuelle : <strong style={{ fontSize: '1.2em' }}>{count}</strong></p>
<button onClick={increment} style={{ marginRight: '10px', padding: '8px 15px' }}>Incrémenter</button>
<button onClick={decrement} style={{ marginRight: '10px', padding: '8px 15px' }}>Décrémenter</button>
<button onClick={reset} style={{ padding: '8px 15px' }}>Réinitialiser</button>
</div>
);
}
export default CounterZustand;
Explication :
useCounterStore((state) => state.count): Ceci est un sélecteur. Il indique à Zustand que ce composant n'est intéressé que par la propriétécountdu store. Si seulecountchange, le composant sera re-rendu. Si d'autres parties du store changent mais pascount, le composant ne sera pas re-rendu, garantissant une performance optimale.- Les actions (
increment,decrement,reset) sont également extraites. Comme ces fonctions sont stables (elles ne changent pas entre les re-renders), elles n'entraîneront pas de re-renders inutiles de ce composant ou de ses enfants.
Avantages et Cas d'Usage de Zustand :
- Simplicité Extrême : Presque pas de boilerplate. L'API est intuitive et facile à apprendre.
- Performance par Défaut : Grâce à ses sélecteurs granulaires, Zustand minimise les re-renders.
- Légèreté : La bibliothèque est très petite, ce qui contribue à la rapidité de chargement de votre application.
- Flexibilité : Peut être utilisé pour l'état global, ou même pour des "sous-stores" locaux à des parties spécifiques de l'application.
- Intégration Facile : S'intègre sans effort dans n'importe quel projet React existant, et même en dehors de React si nécessaire.
Zustand est un excellent choix pour les projets de toutes tailles où la simplicité, la performance et une courbe d'apprentissage minimale sont prioritaires. Il est particulièrement adapté si vous recherchez une alternative légère à Redux sans vouloir vous éloigner trop du concept de "store global" modulaire.
3. Jotai : L'État Atomique et Performant
Jotai (mot japonais signifiant "état" ou "état atomique") est une autre bibliothèque de gestion d'état primitive et flexible pour React. Sa philosophie est de décomposer l'état en unités aussi petites que possible, appelées atomes, et de fournir un modèle mental très simple : lire et écrire des atomes. Jotai est conçu pour être minimaliste, très performant et compatible avec les fonctionnalités avancées de React comme Suspense.
Principes Clés de Jotai :
- Atomes : L'état est représenté par des
atoms. Chaque atome est une unité indépendante d'état qui peut être lue et écrite par les composants. - Dépendances entre Atomes : Les atomes peuvent dépendre d'autres atomes, créant un graphe d'état très puissant. Un atome dérivé est mis à jour uniquement lorsque ses dépendances changent.
- API Basée sur les Hooks (
useAtom) : Les interactions avec les atomes se font via des Hooks spécifiques commeuseAtom,useSetAtomouuseAtomValue. - Performance Maximale : Seuls les composants qui s'abonnent à un atome spécifique sont re-rendus lorsque cet atome change, pas l'ensemble de l'arbre ou des portions plus larges.
- Sans Boilerplate : La création et l'utilisation des atomes sont incroyablement concises.
- Suspense et Concurrent Mode Friendly : Conçu dès le départ pour tirer parti des futures fonctionnalités de React.
Mise en Pratique avec Jotai
Explorons Jotai avec un exemple similaire de compteur, en y ajoutant un atome dérivé pour illustrer sa puissance.
3.1. Installation
npm install jotai
# ou
yarn add jotai
3.2. Définition des Atomes
On définit des atomes pour chaque fragment d'état. Un atome simple a une valeur par défaut. Un atome dérivé est une fonction qui prend get pour lire d'autres atomes.
// atoms/counterAtoms.js
import { atom } from 'jotai';
// Atome simple : la valeur du compteur
export const countAtom = atom(0);
// Atome dérivé : la valeur du compteur doublée
// Cet atome sera mis à jour chaque fois que countAtom change
export const doubledCountAtom = atom((get) => get(countAtom) * 2);
// Atome dérivé : un message basé sur le compteur
export const messageAtom = atom((get) => `Le compteur est à ${get(countAtom)}.`);
Explication :
atom(0): Crée un atome simple avec une valeur initiale de0.atom((get) => get(countAtom) * 2): Crée un atome dérivé. Sa valeur est calculée à partir decountAtom. La fonctiongetpermet de lire la valeur d'autres atomes. Jotai s'assure que cet atome n'est recalculé que sicountAtomchange.
3.3. Utilisation des Atomes dans un Composant React
Pour interagir avec les atomes dans un composant, on utilise le Hook useAtom.
// components/CounterJotai.jsx
import React from 'react';
import { useAtom } from 'jotai';
import { countAtom, doubledCountAtom, messageAtom } from '../atoms/counterAtoms'; // Importez vos atomes
function CounterJotai() {
// useAtom retourne un tableau [valeur, setter] pour un atome lisible/écrivable
const [count, setCount] = useAtom(countAtom);
// Pour les atomes en lecture seule (comme les atomes dérivés), on peut juste récupérer la valeur
const [doubledCount] = useAtom(doubledCountAtom);
const [message] = useAtom(messageAtom);
// Fonctions de mise à jour du compteur
const increment = () => setCount((c) => c + 1);
const decrement = () => setCount((c) => c - 1);
const reset = () => setCount(0);
return (
<div style={{ border: '1px solid #ccc', padding: '15px', borderRadius: '5px' }}>
<h3>Compteur avec Jotai</h3>
<p>Valeur actuelle : <strong style={{ fontSize: '1.2em' }}>{count}</strong></p>
<p>Valeur doublée : <strong>{doubledCount}</strong></p> {/* Affiche l'atome dérivé */}
<p>Message : <em>{message}</em></p> {/* Affiche un autre atome dérivé */}
<button onClick={increment} style={{ marginRight: '10px', padding: '8px 15px' }}>Incrémenter</button>
<button onClick={decrement} style={{ marginRight: '10px', padding: '8px 15px' }}>Décrémenter</button>
<button onClick={reset} style={{ padding: '8px 15px' }}>Réinitialiser</button>
</div>
);
}
export default CounterJotai;
Explication :
useAtom(countAtom): Ce Hook est la porte d'entrée pour interagir avec un atome. Il retourne la valeur actuelle de l'atome et une fonction de mise à jour (similaire àuseState).setCount((c) => c + 1): La fonction de mise à jour peut prendre la nouvelle valeur directement ou une fonction qui reçoit la valeur précédente pour calculer la nouvelle.- Les atomes dérivés (
doubledCountAtom,messageAtom) sont également consommés avecuseAtom. Leur valeur est automatiquement recalculée et les composants qui s'y abonnent sont re-rendus uniquement si la valeur de l'atome dérivé change. C'est l'essence de la performance granulaire de Jotai.
Avantages et Cas d'Usage de Jotai :
- Performance Granulaire Ultime : Grâce au modèle atomique, les re-renders sont extrêmement ciblés. Seuls les composants qui écoutent un atome spécifique sont mis à jour, rendant Jotai idéal pour des applications complexes avec de nombreux états entrelacés.
- Flexibilité et Componisabilité : Les atomes sont des unités de base très flexibles qui peuvent être combinées pour former des états complexes.
- Modèle Mental Simple : "Atomes" est une abstraction simple et puissante.
- Compatibilité Avancée : Entièrement compatible avec Suspense, Concurrent Mode, et d'autres fonctionnalités de pointe de React.
- Moins de Boilerplate que Context/Redux : Très concis et direct.
Jotai brille dans les applications où la performance est critique, où l'état peut être naturellement décomposé en petites unités indépendantes, et où l'on souhaite tirer parti des dernières innovations de React. Il est particulièrement adapté pour remplacer le useState et le useContext pour un état local partagé plus performant.
4. Zustand vs Jotai : Quand Choisir Quoi ?
Zustand et Jotai partagent de nombreuses similitudes : ils sont légers, performants, basés sur des Hooks et visent à réduire le boilerplate. Cependant, leur philosophie sous-jacente et leur manière d'organiser l'état diffèrent, ce qui les rend plus ou moins adaptés à différents scénarios.
Similarités Clés :
- Légèreté : Les deux bibliothèques ont une très faible empreinte mémoire et un bundle size minime.
- Performances : Minimisent les re-renders grâce à une gestion intelligente des abonnements.
- API basée sur les Hooks : S'intègrent nativement et fluidement dans l'écosystème moderne de React.
- TypeScript-friendly : Excellente prise en charge de TypeScript.
- Sans Boilerplate : Très concis comparé aux solutions traditionnelles.
Différences Clés et Critères de Choix :
| Caractéristique | Zustand | Jotai |
| :-------------------- | :----------------------------------------------- | :------------------------------------------------ |
| Philosophie | Store global (avec sélecteurs granulaires) | État atomique (petits fragments d'état) |
| Modèle Mental | Un "magasin" centralisé, que l'on lit et modifie. | Un "graphe d'atomes" interdépendants. |
| Accès à l'état | useStore(selector) pour extraire des parties. | useAtom(monAtome) pour un atome spécifique. |
| Dépendances | Exprimées via des sélecteurs complexes ou des middlewares. | Exprimées naturellement par des atomes dérivés. |
| Boilerplate | Très faible, un seul create par store. | Très faible, un atom par fragment d'état. |
| Composabilité | Peut devenir verbeux pour des sous-états très imbriqués. | Excellente, les atomes s'assemblent facilement. |
| Cas d'Usage Idéal | État global cohésif, applications de taille moyenne, projets cherchant simplicité maximale. | État très granulaire, applications complexes, forte interdépendance d'état, remplacement de useState/useContext optimisé. |
| Courbe d'Apprentissage | Très douce, se rapproche de useState global. | Douce pour les bases, peut nécessiter un changement de paradigme pour les atomes dérivés complexes. |
| Provider Requis | Non (le store est global) | Oui, un <Provider> optionnel pour les atomes non globaux (mais souvent non requis pour les cas d'usage par défaut). |
Quand Choisir Zustand ?
- Si vous préférez un modèle de store global (ou plusieurs stores thématiques) où l'état est bien regroupé et accessible via des sélecteurs.
- Si la simplicité et la rapidité de mise en place sont vos priorités absolues pour un état qui n'est pas excessivement granulaire.
- Si vous recherchez une alternative à Redux qui élimine le boilerplate sans trop s'éloigner du concept de store unique ou modulaire.
- Pour des projets de taille moyenne ou des sous-parties d'applications où un store bien défini suffit.
Quand Choisir Jotai ?
- Si la granularité maximale et la performance sont cruciales, notamment pour des composants qui ne doivent re-render que si l'exacte partie de l'état qu'ils utilisent change.
- Si votre état est très fragmenté, avec de nombreuses petites pièces qui peuvent être combinées et dérivées.
- Si vous souhaitez un remplacement performant et scalable pour
useStateetuseContext, permettant un partage d'état local à n'importe quel niveau de l'arbre. - Si vous êtes à l'aise avec le concept d'atomes et de graphes de dépendances pour structurer votre état.
- Pour des applications très complexes avec des milliers de fragments d'état interdépendants.
En fin de compte, les deux bibliothèques sont d'excellents choix pour la gestion d'état moderne. Le meilleur outil dépendra de la structure de votre application, de la nature de votre état et de vos préférences personnelles en matière de modèle mental.
5. Conclusion et Résumé
Dans cette leçon, nous avons exploré Zustand et Jotai, deux acteurs majeurs de la nouvelle génération de bibliothèques de gestion d'état pour React.
- Zustand offre une approche ultra-minimaliste basée sur un store global (ou modulaire) sans
Provider, avec une API intuitive et des performances optimisées par des sélecteurs intelligents. Il excelle dans la simplification de la gestion d'état pour la plupart des applications, réduisant drastiquement le boilerplate. - Jotai, quant à lui, introduit le concept d'atomes pour une gestion d'état primitive et granulaire. Son architecture atomique permet une performance inégalée en minimisant les re-renders et offre une flexibilité remarquable pour construire des architectures d'état complexes via des atomes dérivés.
Ces deux bibliothèques représentent une évolution significative par rapport aux solutions plus anciennes, en tirant pleinement parti des Hooks de React pour offrir des outils légers, performants et faciles à utiliser. Elles prouvent qu'une gestion d'état avancée n'a pas besoin d'être synonyme de complexité.
Votre choix entre Zustand et Jotai dépendra principalement de la granularité souhaitée pour votre état et du modèle mental que vous préférez. Quoi qu'il en soit, maîtriser l'une ou l'autre de ces bibliothèques vous équipera pour construire des SPAs robustes et performantes, en phase avec les meilleures pratiques de développement moderne. Nous vous encourageons à les expérimenter dans vos projets pour découvrir laquelle s'aligne le mieux avec vos besoins et votre style de développement.