Zustand : Le State Management Minimaliste qui Remplace Redux en 2025
Salut HaWkers, êtes-vous fatigué d'écrire des tonnes de boilerplate pour gérer l'état dans React avec Redux ? Ou confus par la complexité de la Context API pour les états globaux ?
En 2025, Zustand a émergé comme la solution minimaliste que les développeurs React attendaient. Avec seulement ~1KB de taille et une API incroyablement simple, Zustand remplace rapidement Redux dans les nouveaux projets et les migrations.
La bibliothèque a grandi de 300% en adoption l'année dernière, et de grandes entreprises ont déjà migré de Redux vers Zustand, réduisant le code jusqu'à 70% et améliorant la performance.
Le Problème avec Redux et les Alternatives
Redux : Puissant mais Verbeux
// Redux - beaucoup de boilerplate pour quelque chose de simple
// 1. Actions
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
// 2. Action Creators
const increment = () => ({ type: INCREMENT });
const decrement = () => ({ type: DECREMENT });
// 3. Reducer
const counterReducer = (state = { count: 0 }, action) => {
switch (action.type) {
case INCREMENT:
return { count: state.count + 1 };
case DECREMENT:
return { count: state.count - 1 };
default:
return state;
}
};
// 4. Store
const store = createStore(counterReducer);
// 5. Provider
<Provider store={store}>
<App />
</Provider>
// 6. Utilisation dans le composant
function Counter() {
const count = useSelector(state => state.count);
const dispatch = useDispatch();
return (
<div>
{count}
<button onClick={() => dispatch(increment())}>+</button>
</div>
);
}
// ~50 lignes pour un compteur ! 😱Context API : Simple mais avec des Problèmes
// Context API - plus simple, mais re-renders inutiles
const CountContext = createContext();
function CountProvider({ children }) {
const [count, setCount] = useState(0);
// ❌ Problème : TOUS les composants qui utilisent le contexte re-renderent
// même s'ils n'utilisent qu'une partie de l'état
return (
<CountContext.Provider value={{ count, setCount }}>
{children}
</CountContext.Provider>
);
}
function Counter() {
const { count, setCount } = useContext(CountContext);
// Re-render même si une autre valeur du contexte change
return <div>{count}</div>;
}
Zustand : Minimalisme et Performance
Le même compteur en Zustand :
import { create } from 'zustand';
// Définit le store - C'EST TOUT !
const useCounterStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 })
}));
// Utilise dans n'importe quel composant - sans Provider !
function Counter() {
const count = useCounterStore((state) => state.count);
const increment = useCounterStore((state) => state.increment);
return (
<div>
{count}
<button onClick={increment}>+</button>
</div>
);
}
// ~15 lignes, performance optimisée automatiquement ! ✨Avantages :
- ✅ Zéro boilerplate : Pas d'actions, reducers, providers
- ✅ Bundle minuscule : 1KB (Redux: ~10KB)
- ✅ Sans Provider : Fonctionne hors de React aussi
- ✅ Performance : Re-renders optimisés par défaut
- ✅ TypeScript : Support natif excellent
- ✅ DevTools : Intégration avec Redux DevTools
Cas d'Usage Avancés
1. Store d'Authentification Complet
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface User {
id: string;
name: string;
email: string;
}
interface AuthStore {
user: User | null;
token: string | null;
isAuthenticated: boolean;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
updateUser: (user: Partial<User>) => void;
}
export const useAuthStore = create<AuthStore>()(
persist(
(set, get) => ({
user: null,
token: null,
isAuthenticated: false,
login: async (email, password) => {
try {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ email, password })
});
const { user, token } = await response.json();
set({
user,
token,
isAuthenticated: true
});
} catch (error) {
console.error('Login failed:', error);
throw error;
}
},
logout: () => {
set({
user: null,
token: null,
isAuthenticated: false
});
},
updateUser: (updates) => {
set((state) => ({
user: state.user ? { ...state.user, ...updates } : null
}));
}
}),
{
name: 'auth-storage', // Persiste dans localStorage
partialize: (state) => ({ // Persiste uniquement les champs nécessaires
user: state.user,
token: state.token
})
}
)
);
// Utilisation
function Profile() {
const user = useAuthStore((state) => state.user);
const logout = useAuthStore((state) => state.logout);
if (!user) return <Login />;
return (
<div>
<h1>Bonjour, {user.name}</h1>
<button onClick={logout}>Déconnexion</button>
</div>
);
}2. Panier d'Achat avec Calculs Dérivés
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
}
interface CartStore {
items: CartItem[];
// Valeurs calculées
totalItems: () => number;
totalPrice: () => number;
// Actions
addItem: (item: Omit<CartItem, 'quantity'>) => void;
removeItem: (id: string) => void;
updateQuantity: (id: string, quantity: number) => void;
clearCart: () => void;
}
export const useCartStore = create<CartStore>()(
devtools((set, get) => ({
items: [],
// Valeurs calculées comme fonctions
totalItems: () => {
return get().items.reduce((sum, item) => sum + item.quantity, 0);
},
totalPrice: () => {
return get().items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
},
addItem: (newItem) => {
set((state) => {
const existingItem = state.items.find((item) => item.id === newItem.id);
if (existingItem) {
return {
items: state.items.map((item) =>
item.id === newItem.id
? { ...item, quantity: item.quantity + 1 }
: item
)
};
}
return {
items: [...state.items, { ...newItem, quantity: 1 }]
};
});
},
removeItem: (id) => {
set((state) => ({
items: state.items.filter((item) => item.id !== id)
}));
},
updateQuantity: (id, quantity) => {
if (quantity <= 0) {
get().removeItem(id);
return;
}
set((state) => ({
items: state.items.map((item) =>
item.id === id ? { ...item, quantity } : item
)
}));
},
clearCart: () => set({ items: [] })
}))
);
// Utilisation - re-renders optimisés
function Cart() {
// Re-render uniquement si items change
const items = useCartStore((state) => state.items);
const totalPrice = useCartStore((state) => state.totalPrice());
const removeItem = useCartStore((state) => state.removeItem);
return (
<div>
<h2>Panier</h2>
{items.map((item) => (
<div key={item.id}>
{item.name} - {item.quantity}x {item.price}€
<button onClick={() => removeItem(item.id)}>Supprimer</button>
</div>
))}
<p>Total: {totalPrice.toFixed(2)}€</p>
</div>
);
}
function AddToCartButton({ product }) {
// Ce composant NE re-render PAS quand le panier change !
const addItem = useCartStore((state) => state.addItem);
return <button onClick={() => addItem(product)}>Ajouter au Panier</button>;
}
Middleware et Extensions
1. Persist - LocalStorage Automatique
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
const useSettingsStore = create(
persist(
(set) => ({
theme: 'light',
language: 'fr-FR',
notifications: true,
setTheme: (theme) => set({ theme }),
setLanguage: (language) => set({ language }),
toggleNotifications: () =>
set((state) => ({ notifications: !state.notifications }))
}),
{
name: 'app-settings',
storage: createJSONStorage(() => localStorage),
// Migration de versions anciennes
version: 1,
migrate: (persistedState, version) => {
if (version === 0) {
// Migre de v0 à v1
return { ...persistedState, notifications: true };
}
return persistedState;
}
}
)
);2. Immer - Immutabilité Simplifiée
import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';
const useTodoStore = create(
immer((set) => ({
todos: [],
addTodo: (text) =>
set((state) => {
// Avec immer, vous pouvez "muter" directement !
state.todos.push({
id: Date.now(),
text,
completed: false
});
}),
toggleTodo: (id) =>
set((state) => {
const todo = state.todos.find((t) => t.id === id);
if (todo) {
todo.completed = !todo.completed; // Mutation directe !
}
}),
removeTodo: (id) =>
set((state) => {
const index = state.todos.findIndex((t) => t.id === id);
if (index !== -1) {
state.todos.splice(index, 1); // Mutation directe !
}
})
}))
);3. Subscriptions - React Au-Delà de React
// Zustand fonctionne hors des composants React !
import { useUserStore } from './stores/user';
// S'abonne aux changements
const unsubscribe = useUserStore.subscribe(
(state) => state.user,
(user, previousUser) => {
console.log('Utilisateur changé:', { user, previousUser });
// Tracking analytics
if (user && !previousUser) {
analytics.track('User Logged In', { userId: user.id });
}
}
);
// Accède à l'état hors d'un composant
const currentUser = useUserStore.getState().user;
console.log(currentUser);
// Met à jour l'état hors d'un composant
useUserStore.getState().login('user@example.com', 'password');
// Cleanup
unsubscribe();
Zustand vs. Autres Solutions
Comparaison de Taille de Bundle
Redux + Redux Toolkit: ~10KB
MobX: ~16KB
Recoil: ~14KB
Jotai: ~3KB
Zustand: ~1KB ✅
// Zustand + Persist + DevTools: ~3KB (toujours plus petit que les alternatives !)Comparaison de Performance
// Benchmark: 10 000 updates dans une liste de 1000 items
// Redux: ~850ms
// - Re-renders inutiles
// - Normalisation manuelle nécessaire
// Context API: ~1200ms
// - Tous les consommateurs re-renderent
// Zustand: ~420ms ✅
// - Re-renders optimisés par sélecteur
// - Zéro overheadComparaison d'Expérience Développeur
// Redux Toolkit (meilleure DX de Redux)
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => { state.value += 1 },
decrement: (state) => { state.value -= 1 }
}
});
export const { increment, decrement } = counterSlice.actions;
export default counterSlice.reducer;
// Nécessite encore store, provider, etc...
// ~30 lignes de code
// Zustand
const useCounter = create((set) => ({
value: 0,
increment: () => set((s) => ({ value: s.value + 1 })),
decrement: () => set((s) => ({ value: s.value - 1 }))
}));
// ~6 lignes, même résultat ✅Quand NE PAS Utiliser Zustand
✅ Utilisez Zustand quand :
- Besoin d'état global simple
- Voulez une performance optimisée
- Préférez du code minimal
- Travaillez sur un projet React moderne
⚠️ Considérez des alternatives quand :
- L'équipe maîtrise déjà Redux et le projet est grand (coût de migration)
- Besoin de time-travel debugging complexe
- Application legacy avec fort couplage à Redux
Si vous voulez maîtriser React et le state management moderne, je vous recommande l'article Vue Vapor Mode : La Révolution qui Élimine le Virtual DOM où nous explorons les optimisations de performance dans les frameworks modernes.
C'est parti ! 🦅
📚 Maîtrisez React et JavaScript Moderne
Zustand représente l'avenir minimaliste du state management, mais maîtriser React et JavaScript est essentiel pour exploiter les outils modernes au maximum.
Options d'investissement :
- €9,90 (paiement unique)
👉 Découvrir le Guide JavaScript
💡 Matériel mis à jour avec les meilleures pratiques du marché

