Comprendre Redux dans React : Gérer l'état comme un pro

Vous avez du mal à gérer l’état à travers plusieurs composants dans votre application React ? À mesure que votre application se développe, passer des props à travers plusieurs couches de composants (prop drilling) devient difficile à gérer et sujet aux erreurs. Redux offre une solution en fournissant un système de gestion d’état centralisé qui rend vos applications React plus prévisibles et plus faciles à déboguer.
Dans ce guide complet, vous apprendrez comment Redux fonctionne avec React, quand l’utiliser et comment l’implémenter efficacement dans vos projets. Nous couvrirons tout, des concepts fondamentaux à l’implémentation pratique avec Redux Toolkit, la façon moderne d’écrire du code Redux.
Points clés à retenir
- Redux fournit une gestion d’état centralisée pour les applications React
- Utilisez Redux lorsque votre application a un état partagé complexe entre de nombreux composants
- Redux suit un flux de données unidirectionnel : actions → reducers → store → UI
- Redux Toolkit simplifie le développement Redux en réduisant le code répétitif
- L’optimisation des performances est cruciale pour les applications Redux plus importantes
Qu’est-ce que Redux dans React ?
Redux est un conteneur d’état prévisible pour les applications JavaScript, particulièrement populaire avec React. Il stocke l’état complet de votre application dans un objet unique et immuable appelé ""store"". Cette approche centralisée de la gestion d’état résout de nombreux problèmes courants dans les applications React :
- Prop drilling : Plus besoin de faire passer l’état à travers plusieurs couches de composants
- Synchronisation d’état : Assure un état cohérent entre les composants
- Mises à jour prévisibles : Les changements d’état suivent un flux de données unidirectionnel strict
- Débogage : Rend les changements d’état traçables et prévisibles
Redux ne fait pas partie de React lui-même mais fonctionne comme une bibliothèque complémentaire via React Redux, la bibliothèque officielle de liaison UI Redux pour React.
// Basic Redux integration with React
import { Provider } from 'react-redux';
import store from './store';
function App() {
return (
<Provider store={store}>
<YourApplication />
</Provider>
);
}
Quand devriez-vous utiliser Redux ?
Toutes les applications React n’ont pas besoin de Redux. Envisagez d’utiliser Redux lorsque :
- Votre application a une logique d’état complexe partagée entre de nombreux composants
- Les composants doivent accéder et mettre à jour l’état depuis différentes parties de l’arborescence des composants
- Vous devez suivre et déboguer les changements d’état dans toute votre application
- Votre application a une base de code moyenne à grande avec plusieurs développeurs
Pour les applications plus petites ou les composants avec un état localisé, la gestion d’état intégrée à React (useState, useReducer et Context API) est souvent suffisante.
Concepts fondamentaux de Redux
Le Store : Source unique de vérité
Le store Redux contient l’arborescence d’état complète de votre application. Contrairement à l’état des composants React, qui est distribué à travers les composants, Redux centralise tout l’état en un seul endroit :
// Creating a Redux store
import { configureStore } from '@reduxjs/toolkit';
import rootReducer from './reducers';
const store = configureStore({
reducer: rootReducer
});
export default store;
Actions : Décrire les changements d’état
Les actions sont des objets JavaScript simples qui décrivent ce qui s’est passé dans votre application. Elles sont le seul moyen d’envoyer des données au store Redux :
// An action object
{
type: 'counter/incremented',
payload: 1
}
// Action creator function
const increment = (amount) => {
return {
type: 'counter/incremented',
payload: amount
}
}
Chaque action doit avoir une propriété type
qui décrit quel type d’action elle est. La propriété payload
contient toutes les données nécessaires pour l’action.
Reducers : Fonctions pures pour les mises à jour d’état
Les reducers sont des fonctions pures qui prennent l’état actuel et une action, puis retournent un nouvel état. Ils spécifient comment l’état de l’application change en réponse aux actions :
// A simple reducer function
const counterReducer = (state = 0, action) => {
switch (action.type) {
case 'counter/incremented':
return state + action.payload;
case 'counter/decremented':
return state - action.payload;
default:
return state;
}
}
Les reducers doivent :
- Être des fonctions pures (sans effets secondaires)
- Ne jamais modifier l’état directement
- Retourner un nouvel objet d’état lorsque des changements se produisent
Flux de données unidirectionnel
Redux suit un flux de données unidirectionnel strict :
- Vous dispatchez une action depuis un composant
- Le store Redux passe l’action au reducer
- Le reducer crée un nouvel état basé sur l’action
- Le store met à jour son état et notifie tous les composants connectés
- Les composants se re-rendent avec le nouvel état
Ce flux unidirectionnel rend les changements d’état prévisibles et plus faciles à comprendre.
Intégration de React Redux
Provider : Connecter Redux à React
Le composant Provider
rend le store Redux disponible pour tous les composants de votre application :
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
Hooks : Accéder à Redux dans les composants
React Redux fournit des hooks qui permettent à vos composants d’interagir avec le store Redux :
useSelector : Lire l’état
import { useSelector } from 'react-redux';
function CounterDisplay() {
// Select the counter value from the store
const count = useSelector(state => state.counter.value);
return <div>Current count: {count}</div>;
}
Le hook useSelector
:
- Prend une fonction sélecteur qui extrait des données de l’état du store
- Re-rend le composant lorsque l’état sélectionné change
- Optimise les performances en évitant les re-rendus inutiles
useDispatch : Mettre à jour l’état
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './counterSlice';
function Counter() {
const count = useSelector(state => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<div>Count: {count}</div>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
</div>
);
}
Le hook useDispatch
:
- Retourne la fonction dispatch du store
- Vous permet de dispatcher des actions depuis n’importe quel composant
Redux moderne avec Redux Toolkit
Redux Toolkit est la façon officielle et recommandée d’écrire la logique Redux. Il simplifie le développement Redux en :
- Réduisant le code répétitif
- Incluant des utilitaires utiles pour les modèles courants
- Fournissant de bons paramètres par défaut pour la configuration du store
- Permettant l’utilisation de mutations directes de l’état dans les reducers (via Immer)
Créer un store avec configureStore
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './features/counter/counterSlice';
import todosReducer from './features/todos/todosSlice';
const store = configureStore({
reducer: {
counter: counterReducer,
todos: todosReducer
}
});
export default store;
Définir la logique d’état avec createSlice
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
increment: (state) => {
// Redux Toolkit allows us to write ""mutating"" logic in reducers
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
}
}
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
Gérer les opérations asynchrones avec createAsyncThunk
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
// Create an async thunk for fetching data
export const fetchUserData = createAsyncThunk(
'users/fetchUserData',
async (userId, thunkAPI) => {
const response = await fetch(`https://api.example.com/users/${userId}`);
return await response.json();
}
);
const userSlice = createSlice({
name: 'user',
initialState: {
data: null,
status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
error: null
},
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUserData.pending, (state) => {
state.status = 'loading';
})
.addCase(fetchUserData.fulfilled, (state, action) => {
state.status = 'succeeded';
state.data = action.payload;
})
.addCase(fetchUserData.rejected, (state, action) => {
state.status = 'failed';
state.error = action.error.message;
});
}
});
export default userSlice.reducer;
Exemple d’implémentation pratique
Construisons une application de compteur simple avec Redux Toolkit :
1. Configurer le store
// store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './features/counter/counterSlice';
export const store = configureStore({
reducer: {
counter: counterReducer
}
});
2. Créer un slice pour la fonctionnalité de compteur
// features/counter/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
}
}
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
// Selector
export const selectCount = (state) => state.counter.value;
3. Connecter le store à React
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { store } from './store';
import App from './App';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
4. Utiliser Redux dans un composant
// features/counter/Counter.js
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {
increment,
decrement,
incrementByAmount,
selectCount
} from './counterSlice';
export function Counter() {
const count = useSelector(selectCount);
const dispatch = useDispatch();
const [incrementAmount, setIncrementAmount] = useState('2');
return (
<div>
<div>
<button onClick={() => dispatch(decrement())}>-</button>
<span>{count}</span>
<button onClick={() => dispatch(increment())}>+</button>
</div>
<div>
<input
value={incrementAmount}
onChange={(e) => setIncrementAmount(e.target.value)}
/>
<button
onClick={() =>
dispatch(incrementByAmount(Number(incrementAmount) || 0))
}
>
Add Amount
</button>
</div>
</div>
);
}
Techniques d’optimisation des performances
Sélecteurs mémorisés avec Reselect
Reselect (inclus dans Redux Toolkit) vous permet de créer des fonctions sélecteurs mémorisées qui ne recalculent les résultats que lorsque les entrées changent :
import { createSelector } from '@reduxjs/toolkit';
// Basic selectors
const selectItems = state => state.items;
const selectFilter = state => state.filter;
// Memoized selector
export const selectFilteredItems = createSelector(
[selectItems, selectFilter],
(items, filter) => {
// This calculation only runs when items or filter changes
return items.filter(item => item.includes(filter));
}
);
Éviter les rendus inutiles
Pour empêcher les composants de se re-rendre lorsque des parties non liées de l’état changent :
- Sélectionnez uniquement les données spécifiques dont votre composant a besoin
- Utilisez des sélecteurs mémorisés pour les données dérivées
- Utilisez la fonction
shallowEqual
comme second argument deuseSelector
lors de la sélection d’objets
import { useSelector, shallowEqual } from 'react-redux';
// This component will only re-render when user.name or user.email changes
function UserInfo() {
const { name, email } = useSelector(state => ({
name: state.user.name,
email: state.user.email
}), shallowEqual);
return (
<div>
<h2>{name}</h2>
<p>{email}</p>
</div>
);
}
Redux vs. Autres solutions de gestion d’état
Redux vs. Context API
Context API :
- Intégré à React
- Plus simple pour les applications petites à moyennes
- Pas de bibliothèques supplémentaires requises
- Moins de code répétitif
- Outils de débogage limités
Redux :
- Plus puissant pour la logique d’état complexe
- Meilleures performances pour les mises à jour fréquentes
- Excellent débogage avec Redux DevTools
- Support de middleware pour les effets secondaires
- Approche plus structurée de la gestion d’état
Redux vs. Zustand/Recoil/MobX
Considérez ces alternatives lorsque :
- Vous avez besoin d’une API plus simple avec moins de code répétitif (Zustand)
- Vous voulez une gestion d’état basée sur les atomes (Recoil)
- Vous préférez une approche plus orientée objet et réactive (MobX)
Redux reste le meilleur choix lorsque vous avez besoin de :
- Une solution mature et éprouvée
- D’excellentes capacités de débogage
- Un large écosystème de middleware et d’extensions
- Une gestion d’état prévisible pour les applications complexes
Conclusion
Redux fournit une solution de gestion d’état puissante et prévisible pour les applications React. Bien qu’il introduise une certaine complexité et du code répétitif, Redux Toolkit simplifie considérablement l’expérience de développement. Pour les applications complexes avec un état partagé entre de nombreux composants, Redux offre des avantages clairs en termes de maintenabilité, de débogage et de prévisibilité.
Considérez attentivement les besoins de votre application lors du choix d’une solution de gestion d’état. Pour les applications plus simples, la gestion d’état intégrée à React pourrait être suffisante, mais à mesure que votre application gagne en complexité, l’approche structurée de Redux devient de plus en plus précieuse.
FAQ
Utilisez Redux pour les applications complexes avec des mises à jour fréquentes et un état partagé. Utilisez Context pour les applications plus simples ou lorsque vous devez partager des valeurs qui ne changent pas souvent.
Redux a des concepts fondamentaux qui peuvent prendre du temps à comprendre, mais Redux Toolkit simplifie considérablement la courbe d'apprentissage. Commencez par de petits exemples et augmentez progressivement la complexité.
Pas nécessairement. Bien que Redux ajoute une certaine surcharge, il inclut des optimisations de performance et peut en fait améliorer les performances en évitant les re-rendus inutiles.
Utilisez un middleware comme Redux Thunk (inclus dans Redux Toolkit) ou Redux Saga pour des flux asynchrones plus complexes. Le createAsyncThunk de Redux Toolkit rend les opérations asynchrones simples.
Oui. Bien que des hooks comme useReducer et useContext puissent gérer des besoins de gestion d'état plus simples, Redux excelle toujours dans la gestion d'état d'application complexe, en particulier dans les grandes équipes.
Utilisez l'extension Redux DevTools pour inspecter l'état, les actions et les changements d'état au fil du temps. C'est l'une des fonctionnalités les plus puissantes de Redux.