• 15 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 07/03/2022

Initiez-vous aux concepts clés de Redux sur une application simple

Définissez un state Redux adapté à votre application

Le state de Redux est au centre de l'application. Contrairement à React, qui peut contenir de nombreux states, Redux possède un seul et unique state.

Un seul state pour toute l’application ? Ça risque d'être un peu le bazar, non ?

C’est vrai que dit comme ça, ça ressemble un peu à mon App  immense du chapitre précédent. Mais pas de panique ! Redux est fait pour gérer un tel state, et propose des outils adaptés. Ce state unique apporte d’ailleurs deux avantages majeurs :

  • Il est simple de décider où l’on met tel ou tel state global.

  • On peut facilement explorer tous les states globaux de l’application en un coup d'œil, car ils sont tous au même endroit. 

Allez, c’est parti ! Pour commencer, essayons de définir ensemble le state de l’application Tennis Score. Cet exercice ne devrait pas vous poser trop de soucis, car c’est un peu la même logique que pour les states React ! Découvrons Tennis Score dans le screencast ci-dessous :

Dans notre state, il nous faut :

  • le score de chaque joueur ;

  • s'il y a 40-40, savoir quel joueur a l’avantage ;

  • savoir qui a gagné ;

  • savoir si le jeu est en pause.

Alors, vous avez trouvé ? Voici ma solution :

const initialState = {
    // Le score de chacun des joueurs
    player1: 0,
    player2: 0,
    // Si il y a 40-40 quel joueur a l'avantage
    // On utilise null si pas d'avantage
    advantage: null,
    // Qui a gagné ?
    // Si la partie est en cours on utilise null
    winner: null,
    // La partie est-elle en cours ?
    playing: true
};

Maintenant que nous avons notre state, voyons comment on va pouvoir le modifier.

Listez les changements possibles grâce aux actions

Avec React, pour changer le state, il suffit d'appeler setState  avec la nouvelle valeur. C’est par exemple ce que l’on fait dans dans l’exemple du bouton au premier chapitre, lorsque l’on appelle   setCount(count + 1)  .

Dans Redux, c’est différent : on ne modifie pas le state directement. On doit passer par des actions.

J’imagine que ça n’a pas de rapport avec les actions en bourse d’une entreprise ?

Non, en effet ! Une action Redux a une définition bien précise :

  • c’est un objet JavaScript ;

  • il décrit la raison d’un changement de notre state ;

  • cela, grâce à une propriété obligatoire type  ;

  • et il a aussi éventuellement une propriété payload  qui contient des données additionnelles.

Pourquoi on s'embête à passer par une action plutôt que de changer directement le state comme dans React ?

Pour l’illustrer, parmi les deux listes ci-dessous, laquelle permet le mieux de comprendre ce qu’il se passe ?

- { a: 1, b: 0 }           - IncrementA

- { a: 1, b: 1 }           - IncrementB

- { a: 2, b: 1 }           - IncrementA

- { a: 2, b: 3 }           - AddAtoB

- { a: 2, b: 2 }           - DecrementB

- { a: 0, b: 0 }           - Reset

Eh oui, celle de droite ! Elle décrit les changements, alors que celle de gauche montre uniquement le résultat. C’est pour cette raison que l’on utilise des actions dans Redux : cela permet de comprendre les modifications de state, car chaque étape est accompagnée d’une action qui exprime la raison de ce changement !

Dans le cas de notre application de Tennis Score, nous aurons par exemple besoin de l’action suivante pour mettre le jeu en pause ou le reprendre :

// une action Redux est un objet
const playPauseAction = {
    // la propriété type permet d'identifier l'action
    type: "playPause"
};

Lorsqu'un joueur gagne un échange, on pourrait utiliser une des deux actions suivantes :

const player1ScoredAction = { type: 'player1Scored' };
const player2ScoredAction = { type: 'player2Scored' };

Ou même mieux ! Utiliser une seule action avec un payload  pour indiquer quel joueur a marqué :

const pointScoredAction = {
    type: 'pointScored',
    // On utilise payload pour préciser quel joueur a marqué
    payload: { player: "player1" }
};

Pour éviter les erreurs, on peut aussi écrire une fonction qui va créer l’action à notre place.

const playPause = () => ({ type: "playPause" });
const pointScored = (player) => ({
    type: "pointScored",
    payload: { player: player },
});

Utiliser une fonction pour créer une action est très commun dans Redux. Cela a même un nom : “Action Creator”.

Il est maintenant temps d'utiliser ces actions pour changer le state. C’est ce que nous allons faire dans le Reducer !

Faites fonctionner vos actions dans le Reducer

Le Reducer, c’est le cerveau de Redux. C’est là que l'on va mettre la logique de notre application. Un Reducer Redux est une fonction qui reçoit le state et une action en paramètre, et qui retourne un nouveau state.

Un premier state et une action sont envoyés au reducer, qui retourne un nouveau state.
Un premier state et une action sont envoyés au reducer, qui retourne un nouveau state.

Voyons ensemble ce que cela donne concrètement pour notre playPause  :

// le reducer est une fonction
function reducer(state, action) {
    // si l'action est de type playPause...
    if (action.type === "playPause") {
        // ... il faut inverser la propriété playing du state
        return {
            ...state,
            playing: !state.playing
        };
    }
    // sinon on retourne le state sans le changer
    return state;
}

Qu’est-ce que c’est cette syntaxe avec les  ...   ?

Il s’agit d’une syntaxe de destructuring. Cela permet de créer un nouveau state sans modifier le state précédent :

  // on utilise des accolades pour déclarer un nouvel objet
return {
    // on déstructure le state précédent, c'est à dire que l'on
    // copie toutes ses propriété dans notre nouvel objet
    ...state,
    // on remplace la propriété playing par
    // l'inverse de state.playing
    playing: !state.playing
};

On est obligé d’utiliser le destructuring ? Pourquoi ne pas écrire  state.playing = !state.playing   ?

Oui, il faut toujours utiliser le destructuring pour changer le state dans un reducer. Si l’on change directement le state (  state.playing = !state.playing   ), Redux ne peut pas détecter le changement, ce qui posera des problèmes pour la suite.

En revanche, si on ne change pas le state, on peut le retourner directement. C’est ce que l’on fait ici à l’avant-dernière ligne avec  return state  . De cette manière, même en cas d’action invalide, le reducer retourne quand même un state.

Revenons maintenant sur ce que l’on vient de voir dans le screencast ci-dessous :

Vous pouvez explorer le codepen de base utilisé dans le screencast juste ici.

Mais au fait, on parle de Redux depuis le début de ce chapitre, mais on ne l’a toujours pas installé. On va remédier à ça dans la section suivante avec la création du Store !

Assemblez state, action et reducer grâce au Store

Nous avons maintenant tous les ingrédients prêts : le state, les actions et le reducer. Il est temps de les faire fonctionner ensemble grâce au Store !

Le Store de Redux, c’est un peu comme les œufs dans une tarte : c’est ce qui lie tous les ingrédients ensemble.

Commençons par ajouter Redux à notre application. Pour cela, nous allons ajouter la ligne de code suivante en haut de notre fichier JavaScript :

import { createStore } from "https://cdn.skypack.dev/redux@4.0.5";

Ici on importe la fonction createStore  . Cette fonction attend deux paramètres : le reducer et le state initial que nous avons déjà créés dans les sections précédentes !

// on crée le store avec le state et le reducer
const store = createStore(reducer, initialState);

Une fois le store créé, on peut lire le state à tout moment grâce à la fonction  store.getState  :

const state = store.getState();
// { playing: true, ... }

Est-ce qu’il y a aussi une fonction store.setState  pour changer le state ?

Oui, mais cette fonction ne s'appelle pas setState  , mais dispatch  . C’est grâce à elle que l’on va pouvoir utiliser nos actions.

// on passe un objet action
store.dispatch({ type: "playPause" });
// on passe une action depuis une variable
store.dispatch(playPauseAction);
// on passe une action en utilisant un action creator
// attention à bien appeler la fonction pointScored !
store.dispatch(pointScored("player2"));

Si l’on appelle une nouvelle fois getState  , on constate que le state a bien changé !

const state = store.getState();
// { playing: false, ... }

Bravo ! Vous venez de mettre en place la logique Redux !

Une action est envoyée via dispatch, qui donne des informations au Reducer. Le Reducer modifie le state selon ces informations. On peut récupérer le nouveau state avec getState pour mettre à jour l’affichage.
La logique du store Redux

On peut maintenant utiliser getState  pour mettre à jour l’affichage et dispatch  lorsqu’un bouton est cliqué :

pauseButton.addEventListener("click", function () {
    store.dispatch(playPause());
});
const state = store.getState();
updateScoreText(state.playing);

Attention cependant, car contrairement à React, ici l’affichage ne se mettra pas à jour automatiquement. Pour résoudre cela, on peut utiliser la fonction subscribe  du store pour exécuter du code dès que le state change.

La fonction subscribe  attend un seul argument : une fonction à exécuter quand le state change. Dans notre cas, nous allons récupérer le state avec getState et mettre à jour l’affichage avec updateScoreText  :

store.subscribe(
    // cette fonction sera exécutée à chaque fois que le state change
    () => {
        const state = store.getState();
        updateScoreText(state.playing);
    }
);

Revenons sur toutes les étapes qu’il nous faut pour créer et utiliser le store ci-dessous :

Vous avez désormais toutes les cartes en main pour utiliser Redux ! Dans la prochaine section, vous allez pouvoir mettre en pratique ce que vous avez appris !

Exercez-vous

Il est temps pour vous de faire fonctionner l’application Tennis Score. Vous trouverez le codepen de base juste ici. Il contient déjà :

  • la structure HTML de l’application ;

  • le style CSS ;

  • une fonction updateScoreText  qui permet de mettre à jour l’affichage ;

  • un moyen d'exécuter du code lorsque les boutons sont cliqués (  addEventListener  ).

Votre mission, si vous l’acceptez, sera de :

  • créer le state initial de l’application ;

  • imaginer les actions et créer les action creators ;

  • créer un reducer pour implémenter la logique de l’application ;

  • importer Redux et créer le store ;

  • appeler la fonction dispatch  au clic sur les boutons ;

  • utiliser la fonction subscribe  pour automatiquement mettre à jour l’affichage quand le state change.

Si vous avez du mal à vous lancer, vous pouvez regarder de nouveau les screencasts de ce chapitre, ils vous montrent les différentes étapes à effectuer.

Solution

Vous trouverez ci-dessous un screencast qui détaille la solution de cet exercice :

Vous trouverez également le code de ce corrigé à cette adresse CodePen.

En résumé

Redux est organisé en différents concepts :

  • Le state, qui joue le même rôle que le state React, sauf que dans Redux on utilise un seul state global pour toute notre application.

  • Les actions, qui permettent de décrire les différents changements de state.

  • Le reducer, qui utilise le state et les actions pour implémenter la logique de notre application.

  • Et enfin, le Store, qui vient lier tous les éléments ensemble.

Vous commencez à voir les avantages de Redux ? Et pourtant vous n’avez encore rien vu ! En effet, Redux prend tout son sens lorsqu’il est combiné à React ; c’est ce que nous allons voir ensemble dans la partie 2 ! Mais avant de continuer, n’oubliez pas de valider ce que vous avez appris avec le quiz – c’est parti !

Exemple de certificat de réussite
Exemple de certificat de réussite