Logo Redux

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.

Pour les non-initiés

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.

Pour les développeurs

Fonctionnement technique

Redux est basé sur trois principes fondamentaux qui garantissent un flux de données prévisible dans votre application :

  1. Source unique de vérité : L'état de toute l'application est stocké dans un arbre d'objets au sein d'un seul store.
  2. 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é.
  3. 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 Redux
// 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 et Dispatch
// 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 :

Redux Toolkit
// 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 :

Redux Thunk
// 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 :

  1. Un événement se produit dans l'application (clic utilisateur, requête API terminée, etc.)
  2. Une action est dispatchée (dispatch(action))
  3. Le reducer traite l'action et met à jour l'état
  4. Le store notifie tous les composants abonnés
  5. 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
Applications concrètes

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 :

Instagram
Twitter
Airbnb
Netflix
Amazon
Uber
Stripe
Shopify