Contactez-nous

Concept de reducer (inspiré de Redux)

Comprenez le concept fondamental de la fonction Reducer (utilisée par useReducer et Redux) : une fonction pure qui calcule le nouvel état à partir de l'état actuel et d'une action.

L'origine : Le pattern Reducer

Le terme "reducer" provient du monde de la programmation fonctionnelle et est notamment popularisé dans l'écosystème JavaScript par la bibliothèque de gestion d'état Redux. Le hook `useReducer` de React s'inspire directement de ce pattern.

L'idée fondamentale est simple mais puissante : centraliser toute la logique qui détermine comment l'état d'une application (ou d'une partie de celle-ci) change en réponse à des événements (actions) dans une seule et unique fonction. Cette fonction est appelée le reducer.

Un reducer est une fonction qui respecte une signature spécifique : elle prend deux arguments, l'état actuel (currentState) et une action (action), et elle retourne le prochain état (newState).

function monReducer(currentState, action) {
  // Logique pour calculer le nouvel état basé sur currentState et action
  const newState = /* ... */;
  return newState;
}

Le nom "reducer" vient de la méthode Array.prototype.reduce() en JavaScript. Tout comme `reduce` prend un accumulateur et une valeur courante pour produire une nouvelle valeur d'accumulateur, un reducer prend l'état actuel (l'accumulateur) et une action (la valeur courante) pour produire le nouvel état.

Les caractéristiques clés d'un Reducer

1. Fonction Pure :

C'est la caractéristique la plus importante. Un reducer doit être une fonction pure. Cela signifie que :

  • Etant donné les mêmes entrées (même `currentState` et même `action`), il doit toujours retourner la même sortie (`newState`).
  • Il ne doit avoir aucun effet de bord. Il ne doit pas :
    • Modifier ses arguments (l'état actuel ou l'action). L'immutabilité est cruciale !
    • Effectuer des appels API ou interagir avec le stockage local.
    • Appeler des fonctions non pures (comme `Math.random()` ou `Date.now()` si le résultat affecte le nouvel état).
    • Modifier des variables en dehors de sa propre portée.

La pureté garantit que les transitions d'état sont prévisibles, déterministes et faciles à tester et à déboguer.

2. Immutabilité :

Puisqu'un reducer ne doit pas modifier l'état actuel, il doit toujours retourner une nouvelle instance de l'état lorsqu'une modification est nécessaire. Si l'état est un objet ou un tableau, cela signifie qu'il faut créer une copie (par exemple, en utilisant la syntaxe de décomposition `...` ou des méthodes comme `map`, `filter`) et appliquer les changements sur cette copie.

function todoReducer(state, action) {
  switch (action.type) {
    case 'ADD_TODO':
      // Retourne un NOUVEAU tableau avec la nouvelle tâche ajoutée
      return [...state, { id: Date.now(), text: action.payload, completed: false }];
    case 'TOGGLE_TODO':
      // Retourne un NOUVEAU tableau où la tâche concernée est un NOUVEL objet
      return state.map(todo =>
        todo.id === action.payload.id ? { ...todo, completed: !todo.completed } : todo
      );
    // !!! MAUVAIS : Mutation directe de l'état !!!
    // case 'ADD_TODO_MUTATION': 
    //   state.push({ id: Date.now(), text: action.payload, completed: false });
    //   return state; // Retourne l'état muté, ce qui est incorrect !
    default:
      return state;
  }
}
3. Gestion des Actions :

Le reducer utilise généralement l'information contenue dans l'objet `action` (principalement la propriété `type`) pour décider comment calculer le nouvel état. Une structure courante est une instruction `switch` sur `action.type`.

Si le reducer reçoit une action qu'il ne reconnaît pas, il doit retourner l'état actuel sans modification (la clause `default` dans le `switch`).

Le flux de données avec un Reducer

Dans le contexte de `useReducer` (ou Redux), le flux typique est le suivant :

  1. Evénement UI : L'utilisateur interagit avec l'interface (clic sur un bouton, saisie dans un champ...).
  2. Appel `dispatch(action)` : Le gestionnaire d'événement appelle la fonction `dispatch` en lui passant un objet `action` qui décrit l'interaction.
  3. Exécution du Reducer : React (ou Redux) prend l'état actuel et l'action reçue, et appelle votre fonction `reducer` avec ces deux arguments.
  4. Calcul du Nouvel Etat : Le reducer exécute sa logique (basée sur le type d'action) et retourne le nouvel état calculé (en respectant la pureté et l'immutabilité).
  5. Mise à jour de l'Etat : React (ou Redux) remplace l'ancien état par le nouvel état retourné par le reducer.
  6. Re-rendu : React déclenche un re-rendu des composants qui dépendent de cet état, leur fournissant la nouvelle valeur.

Ce flux unidirectionnel et explicite rend l'évolution de l'état beaucoup plus facile à suivre et à déboguer.

Conclusion : La pierre angulaire de la gestion d'état structurée

Le concept de reducer est fondamental pour comprendre `useReducer` et des bibliothèques comme Redux. En imposant une structure où toute la logique de transition d'état est concentrée dans une fonction pure et immuable qui répond à des actions descriptives, le pattern Reducer apporte ordre, prévisibilité et testabilité à la gestion d'état, en particulier lorsque celle-ci devient complexe.

Maîtriser l'écriture de fonctions reducer pures et immuables est la clé pour utiliser efficacement `useReducer` et pour structurer la logique d'état de manière robuste dans vos applications React.