Redux
Une bibliothèque de gestion d'état prévisible pour applications JavaScript, qui vous aide à créer des applications qui se comportent de manière cohérente, s'exécutent dans différents environnements et sont faciles à tester.
Qu'est-ce que Redux ?
Imaginez que vous développez une application web complexe, comme un réseau social ou une plateforme de commerce électronique. Ces applications contiennent beaucoup de données et d'états : qui est connecté, qu'y a-t-il dans le panier, quels messages ont été lus, etc.
Sans un système de gestion d'état organisé, ces informations peuvent rapidement devenir difficiles à gérer, entraînant des bugs et rendant l'application imprévisible.
Redux est comme un entrepôt centralisé pour toutes les données de votre application. Au lieu d'avoir des informations éparpillées partout, tout est stocké à un seul endroit, avec des règles claires sur comment les modifier.
Pourquoi utiliser Redux ?
Prévisibilité
Avec Redux, les changements d'état se produisent toujours de la même façon, ce qui rend l'application plus fiable et plus facile à déboguer.
Maintenabilité
La structure imposée par Redux aide à organiser le code et facilite la maintenance à long terme, surtout pour les grandes équipes.
En résumé, Redux est particulièrement utile pour les applications moyennes à grandes où la gestion de l'état devient complexe. Il ajoute de la structure et de la rigueur qui permettent aux développeurs de garder le contrôle sur l'évolution des données dans l'application.
Fonctionnement technique
Redux est basé sur trois principes fondamentaux qui garantissent un flux de données prévisible dans votre application :
- Source unique de vérité : L'état de toute l'application est stocké dans un arbre d'objets au sein d'un seul store.
- L'état est en lecture seule : La seule façon de modifier l'état est d'émettre une action qui décrit ce qui s'est passé.
- Les changements sont faits par des fonctions pures : Les réducteurs (reducers) sont des fonctions pures qui prennent l'état précédent et une action, et retournent le nouvel état.
Architecture Redux
Store, Actions et Reducers
Voici comment créer un store Redux basique :
// store.js
import { createStore } from 'redux';
// 1. Définir l'état initial
const initialState = {
counter: 0,
todos: []
};
// 2. Créer un reducer (pure function)
function rootReducer(state = initialState, action) {
switch (action.type) {
case 'INCREMENT':
return {
...state,
counter: state.counter + 1
};
case 'DECREMENT':
return {
...state,
counter: state.counter - 1
};
case 'ADD_TODO':
return {
...state,
todos: [...state.todos, action.payload]
};
default:
return state;
}
}
// 3. Créer le store
const store = createStore(rootReducer);
export default store;
Dispatcher des actions
Voici comment définir et dispatcher des actions pour modifier l'état :
// actions.js
export const increment = () => ({
type: 'INCREMENT'
});
export const decrement = () => ({
type: 'DECREMENT'
});
export const addTodo = (text) => ({
type: 'ADD_TODO',
payload: {
id: new Date().getTime(),
text,
completed: false
}
});
// Usage dans un composant
import { useDispatch, useSelector } from 'react-redux';
import { increment, addTodo } from './actions';
function MyComponent() {
const dispatch = useDispatch();
const counter = useSelector(state => state.counter);
const todos = useSelector(state => state.todos);
return (
<div>
<p>Compteur: {counter}</p>
<button onClick={() => dispatch(increment())}>
Incrémenter
</button>
<button onClick={() => dispatch(addTodo('Nouvelle tâche'))}>
Ajouter une tâche
</button>
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
</div>
);
}
Redux Toolkit
Redux Toolkit est l'approche recommandée pour écrire la logique Redux. Il simplifie le code et intègre des bonnes pratiques :
// store.js avec Redux Toolkit
import { configureStore, createSlice } from '@reduxjs/toolkit';
// Slice pour le compteur (combine reducers et actions)
const counterSlice = createSlice({
name: 'counter',
initialState: 0,
reducers: {
increment: state => state + 1,
decrement: state => state - 1,
incrementByAmount: (state, action) => state + action.payload
}
});
// Slice pour les todos
const todosSlice = createSlice({
name: 'todos',
initialState: [],
reducers: {
addTodo: (state, action) => {
// Redux Toolkit utilise Immer qui permet une syntaxe "mutable"
// mais qui produit un état immuable en arrière-plan
state.push({
id: new Date().getTime(),
text: action.payload,
completed: false
});
},
toggleTodo: (state, action) => {
const todo = state.find(todo => todo.id === action.payload);
if (todo) {
todo.completed = !todo.completed;
}
}
}
});
// Export des actions
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export const { addTodo, toggleTodo } = todosSlice.actions;
// Création du store
const store = configureStore({
reducer: {
counter: counterSlice.reducer,
todos: todosSlice.reducer
}
});
export default store;
Actions asynchrones avec Redux Thunk
Redux Thunk permet de gérer les appels API et autres opérations asynchrones :
// thunks.js - Actions asynchrones avec Redux Thunk
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
// Création d'un thunk pour une requête API
export const fetchTodos = createAsyncThunk(
'todos/fetchTodos',
async (_, { rejectWithValue }) => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/todos?_limit=10');
if (!response.ok) throw new Error('Server Error');
const data = await response.json();
return data;
} catch (error) {
return rejectWithValue(error.message);
}
}
);
// Slice avec gestion des états de chargement
const todosSlice = createSlice({
name: 'todos',
initialState: {
items: [],
status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
error: null
},
reducers: {
// ... autres reducers
},
extraReducers: (builder) => {
builder
.addCase(fetchTodos.pending, (state) => {
state.status = 'loading';
})
.addCase(fetchTodos.fulfilled, (state, action) => {
state.status = 'succeeded';
state.items = action.payload;
})
.addCase(fetchTodos.rejected, (state, action) => {
state.status = 'failed';
state.error = action.payload;
});
}
});
// Usage dans un composant
function TodoList() {
const dispatch = useDispatch();
const { items, status, error } = useSelector(state => state.todos);
useEffect(() => {
if (status === 'idle') {
dispatch(fetchTodos());
}
}, [status, dispatch]);
if (status === 'loading') return <div>Chargement...</div>;
if (status === 'failed') return <div>Erreur: {error}</div>;
return (
<ul>
{items.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
);
}
Flux de données dans Redux
Le flux de données dans Redux suit toujours le même schéma unidirectionnel :
- Un événement se produit dans l'application (clic utilisateur, requête API terminée, etc.)
- Une action est dispatchée (dispatch(action))
- Le reducer traite l'action et met à jour l'état
- Le store notifie tous les composants abonnés
- Les composants abonnés se mettent à jour avec le nouvel état
Écosystème Redux
- Redux Toolkit - La façon moderne de développer avec Redux, qui simplifie le code et inclut des outils utiles
- React-Redux - Bibliothèque pour intégrer Redux avec React facilement
- Redux Thunk - Middleware pour gérer la logique asynchrone
- Redux Saga - Alternative à Thunk pour la gestion d'effets secondaires complexes
- Redux DevTools - Extension pour déboguer les applications Redux
- RTK Query - Outil dans Redux Toolkit pour simplifier la récupération et la mise en cache des données
- Redux Persist - Pour persister et rehydrater votre store
Bonnes pratiques
- Utilisez Redux Toolkit comme point de départ
- Structurez votre code Redux par "features" plutôt que par type
- Maintenez vos reducers purs et sans effets secondaires
- Utilisez des sélecteurs pour accéder à l'état et éviter les duplications
- Normalisez l'état complexe (objets imbriqués)
- N'utilisez pas Redux pour tout ! Certains états peuvent rester locaux
Cas d'usage
Tableaux de bord complexes
Idéal pour les applications avec de nombreux widgets et visualisations qui doivent partager des données et des états.
E-commerce
Parfait pour la gestion des paniers, filtres de produits, favoris et processus de commande complexes.
Réseaux sociaux
Idéal pour gérer les fils d'actualité, messageries, notifications et autres fonctionnalités interactives.
Outils de collaboration
Excellent pour les applications d'édition collaborative, gestion de projet et outils de productivité d'équipe.
Entreprises qui utilisent Redux
Redux est largement utilisé dans l'industrie pour des applications critiques :