Contactez-nous

Redux Toolkit : L'approche moderne et simplifiée

Découvrez Redux Toolkit (RTK), la solution officielle pour écrire du code Redux efficace avec moins de boilerplate, incluant configureStore, createSlice et createAsyncThunk.

Pourquoi redux toolkit ? Adresser les critiques de redux classique

Bien que Redux soit une solution puissante et éprouvée pour la gestion d'état, son implémentation "classique" a souvent été critiquée pour sa verbosité et la quantité de code répétitif (boilerplate) nécessaire pour configurer le store, définir les actions, les créateurs d'actions et les reducers. La configuration des middlewares (comme Thunk ou Saga) et l'intégration des DevTools nécessitaient également plusieurs étapes manuelles.

Face à ces constats, l'équipe Redux a développé Redux Toolkit (RTK). Il ne s'agit pas d'une nouvelle bibliothèque remplaçant Redux, mais plutôt d'un ensemble d'outils officiels, recommandés et "opinionated" (qui font des choix pour vous) conçus pour simplifier drastiquement le développement avec Redux. RTK intègre les meilleures pratiques, réduit le boilerplate et offre une expérience de développement beaucoup plus agréable et productive. Aujourd'hui, Redux Toolkit est la manière standard et recommandée d'utiliser Redux.

`configureStore` : Simplifier la configuration du store

L'une des premières améliorations majeures apportées par RTK est la fonction `configureStore`. Elle remplace la fonction `createStore` de Redux classique et simplifie grandement la configuration :

  • Combine les reducers automatiquement : Vous passez un objet où les clés sont les noms des tranches (slices) de votre état et les valeurs sont les fonctions reducer correspondantes. `configureStore` appelle `combineReducers` pour vous en coulisses.
  • Ajoute les middlewares par défaut : Il inclut automatiquement plusieurs middlewares utiles, notamment `redux-thunk` pour gérer la logique asynchrone (vous pouvez bien sûr ajouter les vôtres).
  • Active l'extension Redux DevTools : L'intégration avec l'extension de navigateur Redux DevTools est activée par défaut en mode développement, sans configuration supplémentaire.
  • Ajoute des vérifications en développement : Il inclut des middlewares qui détectent les erreurs courantes, comme la mutation accidentelle de l'état dans les reducers.
// app/store.js
import { configureStore } from '@reduxjs/toolkit';
// Importe les reducers générés par createSlice (voir section suivante)
import counterReducer from '../features/counter/counterSlice';
import userReducer from '../features/user/userSlice';

export const store = configureStore({
  reducer: {
    // Clé: nom de la tranche d'état, Valeur: fonction reducer
    counter: counterReducer,
    user: userReducer,
    // Ajoutez d'autres reducers ici
  },
  // Pas besoin de configurer Thunk ou DevTools manuellement ici
});

// Types pour TypeScript (optionnel mais recommandé)
// export type RootState = ReturnType;
// export type AppDispatch = typeof store.dispatch;

Cette seule fonction remplace plusieurs étapes de configuration manuelles de Redux classique.

`createSlice` : Révolutionner la création de reducers et d'actions

C'est sans doute la fonctionnalité la plus emblématique de RTK. `createSlice` est une fonction qui accepte un nom pour la tranche d'état, un état initial, et un objet contenant les fonctions reducer. Elle génère automatiquement :

  • Les types d'actions (basés sur le nom du slice et le nom de la fonction reducer, ex: `'counter/increment'`).
  • Les créateurs d'actions correspondants (ex: `increment()`).
  • La fonction reducer pour cette tranche d'état.

De plus, `createSlice` utilise la bibliothèque Immer en interne. Cela vous permet d'écrire une logique de mise à jour qui ressemble à une mutation directe de l'état (ex: `state.value += 1`), mais Immer s'assure en coulisses que l'état n'est pas réellement muté et qu'une nouvelle copie immuable est bien retournée, respectant ainsi les principes de Redux.

// features/counter/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';

const initialState = {
  value: 0,
  status: 'idle',
};

export const counterSlice = createSlice({
  name: 'counter', // Nom du slice
  initialState, // Etat initial
  // Objet contenant les fonctions reducer et leurs noms
  reducers: {
    // Chaque clé ici devient un type d'action ('counter/increment')
    // et un créateur d'action (increment())
    increment: (state) => {
      // Logique "mutable" grâce à Immer !
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    // Reducer qui accepte un payload
    incrementByAmount: (state, action) => { 
      // action.payload contient la valeur passée au créateur d'action
      state.value += action.payload;
    },
  },
  // Voir 'extraReducers' pour gérer les actions externes ou createAsyncThunk
});

// Exporte les créateurs d'actions générés automatiquement
export const { increment, decrement, incrementByAmount } = counterSlice.actions;

// Exporte le reducer généré
export default counterSlice.reducer;

Ce code remplace la définition manuelle des constantes de type d'action, des créateurs d'actions, et l'écriture d'un switch/case dans le reducer, tout en simplifiant la logique de mise à jour grâce à Immer.

`createAsyncThunk` : Standardiser la logique asynchrone

Pour gérer les opérations asynchrones (comme les appels API) de manière cohérente, RTK propose `createAsyncThunk`. Cette fonction accepte un préfixe de type d'action et une fonction "payload creator" qui doit retourner une promesse (contenant généralement le résultat de l'appel API).

`createAsyncThunk` génère et dispatche automatiquement trois types d'actions correspondant aux différents états de la promesse :

  • `pending`: Lorsque l'opération asynchrone démarre.
  • `fulfilled`: Lorsque la promesse réussit (résultat disponible dans `action.payload`).
  • `rejected`: Lorsque la promesse échoue (erreur disponible dans `action.error`).

Vous pouvez ensuite écouter ces actions dans votre `createSlice` en utilisant le champ `extraReducers` pour mettre à jour l'état en conséquence (par exemple, définir un état de chargement, stocker les données reçues, ou enregistrer l'erreur).

// features/user/userSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { fetchUserDataAPI } from './userAPI'; // Fonction simulant l'appel API

// Création du thunk asynchrone
export const fetchUserById = createAsyncThunk(
  'user/fetchByIdStatus', // Préfixe pour les types d'actions (pending, fulfilled, rejected)
  async (userId, thunkAPI) => {
    // La fonction payload creator
    const response = await fetchUserDataAPI(userId);
    return response.data; // La valeur retournée devient le payload de l'action 'fulfilled'
  }
);

const initialState = {
  profile: null,
  status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
  error: null,
};

export const userSlice = createSlice({
  name: 'user',
  initialState,
  reducers: {
    // ... reducers synchrones si nécessaire ...
  },
  // Gère les actions générées par createAsyncThunk
  extraReducers: (builder) => {
    builder
      .addCase(fetchUserById.pending, (state) => {
        state.status = 'loading';
        state.error = null;
      })
      .addCase(fetchUserById.fulfilled, (state, action) => {
        state.status = 'succeeded';
        state.profile = action.payload; // Met à jour l'état avec les données reçues
      })
      .addCase(fetchUserById.rejected, (state, action) => {
        state.status = 'failed';
        state.error = action.error.message;
      });
  },
});

export default userSlice.reducer;

Cette approche structure la gestion des états de chargement, de succès et d'erreur pour les opérations asynchrones.

Conclusion : Les avantages de Redux Toolkit

En résumé, Redux Toolkit (RTK) transforme l'expérience de développement avec Redux :

  • Réduction drastique du boilerplate : Moins de code à écrire pour la configuration, les actions et les reducers.
  • Simplicité : Des API comme `configureStore` et `createSlice` sont plus faciles à comprendre et à utiliser.
  • Bonnes pratiques intégrées : Immer pour l'immutabilité simplifiée, Thunk pour l'asynchrone, intégration des DevTools, détection d'erreurs.
  • Opinionated mais flexible : Il guide vers une structure efficace mais permet toujours la personnalisation si nécessaire.

Pour tout nouveau projet utilisant Redux, ou pour la modernisation d'un projet existant, Redux Toolkit est la voie à suivre. Il conserve la puissance et la prévisibilité de Redux tout en éliminant une grande partie de sa complexité historique.