You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Web-Dev-For-Beginners/7-bank-project/4-state-management/translations/README.fr.md

282 lines
17 KiB

# Créer une application bancaire Partie 4: Concepts de gestion d'état
## Quiz préalable
[Quiz préalable](https://calm-wave-0d1a32b03.1.azurestaticapps.net/quiz/47?loc=fr)
### Introduction
Au fur et à mesure quune application Web se développe, il devient difficile de suivre tous les flux de données. Quel code obtient les données, quelle page les consomme, où et quand doit-il être mis à jour... il est facile de se retrouver avec un code désordonné et difficile à maintenir. Cela est particulièrement vrai lorsque vous devez partager des données entre différentes pages de votre application, par exemple des données utilisateur. Le concept de *gestion de létat* a toujours existé dans toutes sortes de programmes, mais comme les applications Web ne cessent de croître en complexité, cest maintenant un point clé à prendre en compte pendant le développement.
Dans cette dernière partie, nous examinerons lapplication que nous avons créée pour repenser la façon dont létat est géré, permettant la prise en charge de lactualisation du navigateur à tout moment et la persistance des données entre les sessions utilisateur.
### Prérequis
Vous devez avoir terminé la [récupération des données](../../3-data/translations/README.fr.md) de lapplication web pour cette leçon. Vous devez également installer [Node.js](https://nodejs.org) et [exécuter lAPI du serveur](../../api/translations/README.fr.md) localement afin que vous puissiez gérer les données du compte.
Vous pouvez tester que le serveur fonctionne correctement en exécutant cette commande dans un terminal:
```sh
curl http://localhost:5000/api
# -> doit renvoyer "Bank API v1.0.0" comme résultat
```
---
## Repenser la gestion des états
Dans la [leçon précédente](../../3-data/translations/README.fr.md), nous avons introduit un concept basique détat dans notre application avec la variable globale `account` qui contient les données bancaires de lutilisateur actuellement connecté. Cependant, notre mise en œuvre actuelle présente certains défauts. Essayez dactualiser la page lorsque vous êtes sur le tableau de bord. Que se passe-t-il?
Il y a 3 problèmes avec le code actuel:
- Létat nest pas persistant, car une actualisation du navigateur vous ramène à la page de connexion.
- Il existe plusieurs fonctions qui modifient létat. Au fur et à mesure que lapplication se développe, il peut être difficile de suivre les modifications et il est facile doublier den mettre à jour une.
- Létat nest pas nettoyé, donc lorsque vous cliquez sur *Logout* les données du compte sont toujours là même si vous êtes sur la page de connexion.
Nous pourrions mettre à jour notre code pour résoudre ces problèmes un par un, mais cela créerait plus de duplication de code et rendrait lapplication plus complexe et difficile à maintenir. Ou nous pourrions faire une pause de quelques minutes et repenser notre stratégie.
> Quels problèmes essayons-nous vraiment de résoudre ici?
[La gestion de létat](https://en.wikipedia.org/wiki/State_management) consiste à trouver une bonne approche pour résoudre ces deux problèmes particuliers:
- Comment rendre compréhensibles les flux de données dans une application?
- Comment garder les données détat toujours synchronisées avec linterface utilisateur (et vice versa)?
Une fois que vous vous êtes occupé de ceux-ci, tous les autres problèmes que vous pourriez avoir peuvent être déjà résolus ou sont devenus plus faciles à résoudre. Il existe de nombreuses approches possibles pour résoudre ces problèmes, mais nous opterons pour une solution commune qui consiste à **centraliser les données et les moyens de les modifier**. Les flux de données se dérouleraient comme suit:
![Schéma montrant les flux de données entre le code HTML, les actions de lutilisateur et létat](../images/data-flow.png)
> Nous ne couvrirons pas ici la partie où les données déclenchent automatiquement la mise à jour de la vue, car elle est liée à des concepts plus avancés de [Programmation réactive](https://en.wikipedia.org/wiki/Reactive_programming). Cest un bon sujet de suivi si vous êtes prêt à plonger profondément.
✅ Il existe de nombreuses bibliothèques avec différentes approches de la gestion des États, [Redux](https://redux.js.org) étant une option populaire. Jetez un coup dœil aux concepts et aux modèles utilisés, car cest souvent un bon moyen dapprendre quels problèmes potentiels vous pouvez rencontrer dans les grandes applications Web et comment ils peuvent être résolus.
### Tâche
Nous allons commencer par un peu de refactorisation. Remplacer la déclaration `account`:
```js
let account = null;
```
Par:
```js
let state = {
account: null
};
```
Lidée est de *centraliser* toutes nos données dapplication dans un seul objet détat. Nous navons que le `account` pour linstant dans létat, donc cela ne change pas beaucoup, mais cela crée un chemin pour les évolutions.
Nous devons également mettre à jour les fonctions en lutilisant. Dans les fonctions `register()` et `login()`, remplacez `account = ...` par `state.account = ...`;
En haut de la fonction `updateDashboard()`, ajoutez cette ligne:
```js
const account = state.account;
```
Ce refactoring en lui-même na pas apporté beaucoup daméliorations, mais lidée était de jeter les bases des prochains changements.
## Suivre les modifications de données
Maintenant que nous avons mis en place lobjet `state` pour stocker nos données, létape suivante consiste à centraliser les mises à jour. Lobjectif est de faciliter le suivi des changements et de leur moment.
Pour éviter que des modifications soient apportées à lobjet `state`, il est également recommandé de le considérer comme [*immuable*](https://en.wikipedia.org/wiki/Immutable_object), ce qui signifie quil ne peut pas être modifié du tout. Cela signifie également que vous devez créer un nouvel objet détat si vous souhaitez y modifier quoi que ce soit. Ce faisant, vous créez une protection contre les [effets secondaires](https://en.wikipedia.org/wiki/Side_effect_(computer_science)) potentiellement indésirables et ouvrez des possibilités de nouvelles fonctionnalités dans votre application, telles que la mise en œuvre de lannulation/rétablissement, tout en facilitant le débogage. Par exemple, vous pouvez consigner chaque modification apportée à létat et conserver un historique des modifications pour comprendre la source dun bogue.
En JavaScript, vous pouvez utiliser [`Object.freeze()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) pour créer une version immuable dun objet. Si vous essayez dapporter des modifications à un objet immuable, une exception sera déclenchée.
✅ Connaissez-vous la différence entre un objet immuable *peu profond* et un objet immuable *profond*? Vous pouvez en apprendre plus sur ce sujet [ici](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze#What_is_shallow_freeze).
### Tâche
Créons une nouvelle fonction `updateState()`:
```js
function updateState(property, newData) {
state = Object.freeze({
...state,
[property]: newData
});
}
```
Dans cette fonction, nous créons un nouvel objet détat et copions les données de létat précédent à laide de [*lopérateur spread (`...`)*](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Spread_in_object_literals). Ensuite, nous remplaçons une propriété particulière de lobjet détat par les nouvelles données en utilisant la [notation entre crochets](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Working_with_Objects#Objects_and_properties) `[property]` pour laffectation. Enfin, nous verrouillons lobjet pour empêcher les modifications en utilisant `Object.freeze()`. Nous navons que la propriété `account` stockée dans létat pour linstant, mais avec cette approche, vous pouvez ajouter autant de propriétés que nécessaire dans létat.
Nous allons également mettre à jour linitialisation `state` pour nous assurer que létat initial est également gelé:
```js
let state = Object.freeze({
account: null
});
```
Après cela, mettez à jour la fonction `register` en remplaçant laffectation `state.account = result;` par:
```js
updateState('account', result);
```
Faites de même avec la fonction `login`, en remplaçant `state.account = data;` par:
```js
updateState('account', data);
```
Nous allons maintenant saisir loccasion de résoudre le problème des données de compte qui ne sont pas effacées lorsque lutilisateur clique sur *Logout*.
Créez une nouvelle fonction `logout()`:
```js
function logout() {
updateState('account', null);
navigate('/login');
}
```
Dans `updateDashboard()`, remplacez la redirection `return navigate('/login');` par `return logout()`;
Essayez denregistrer un nouveau compte, de vous déconnecter et de vous reconnecter pour vérifier que tout fonctionne toujours correctement.
> Conseil: vous pouvez jeter un coup dœil à tous les changements détat en ajoutant `console.log(state)` au bas de `updateState()` et en ouvrant la console dans les outils de développement de votre navigateur.
## Conserver létat
La plupart des applications Web doivent conserver les données pour pouvoir fonctionner correctement. Toutes les données critiques sont généralement stockées dans une base de données et accessibles via une API de serveur, comme les données du compte utilisateur dans notre cas. Mais parfois, il est également intéressant de conserver certaines données sur lapplication cliente qui sexécute dans votre navigateur, pour une meilleure expérience utilisateur ou pour améliorer les performances de chargement.
Lorsque vous souhaitez conserver des données dans votre navigateur, vous devez vous poser quelques questions importantes:
- *Les données sont-elles sensibles?* Vous devez éviter de stocker des données sensibles sur le client, telles que les mots de passe des utilisateurs.
- *Pendant combien de temps devez-vous conserver ces données?* Prévoyez-vous daccéder à ces données uniquement pour la session en cours ou souhaitez-vous quelles soient stockées pour toujours?
Il existe plusieurs façons de stocker des informations dans une application web, en fonction de ce que vous souhaitez réaliser. Par exemple, vous pouvez utiliser les URL pour stocker une requête de recherche et la rendre partageable entre les utilisateurs. Vous pouvez également utiliser des [cookies HTTP](https://developer.mozilla.org/docs/Web/HTTP/Cookies) si les données doivent être partagées avec le serveur, comme les informations d'[authentification](https://en.wikipedia.org/wiki/Authentication).
Une autre option consiste à utiliser lune des nombreuses API de navigateur pour stocker des données. Deux dentre eux sont particulièrement intéressants:
- [`localStorage`](https://developer.mozilla.org/docs/Web/API/Window/localStorage): un [Key/Value store](https://en.wikipedia.org/wiki/Key%E2%80%93value_database) permettant de conserver des données spécifiques au site web actuel sur différentes sessions. Les données qui y sont enregistrées nexpirent jamais.
- [`sessionStorage`](https://developer.mozilla.org/docs/Web/API/Window/sessionStorage): celui-ci fonctionne de la même manière que `localStorage` sauf que les données qui y sont stockées sont effacées à la fin de la session (lorsque le navigateur est fermé).
Notez que ces deux API autorisent uniquement le stockage de [chaînes](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String). Si vous souhaitez stocker des objets complexes, vous devrez les sérialiser au format [JSON](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON) à laide de [`JSON.stringify()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify).
✅ Si vous souhaitez créer une application web qui ne fonctionne pas avec un serveur, il est également possible de créer une base de données sur le client à laide de [lAPI `IndexedDB`](https://developer.mozilla.org/docs/Web/API/IndexedDB_API). Celui-ci est réservé aux cas dutilisation avancés ou si vous avez besoin de stocker une quantité importante de données, car il est plus complexe à utiliser.
### Tâche
Nous voulons que nos utilisateurs restent connectés jusquà ce quils cliquent explicitement sur le bouton *Logout*, nous utiliserons donc `localStorage` pour stocker les données du compte. Tout dabord, définissons une clé que nous utiliserons pour stocker nos données.
```js
const storageKey = 'savedAccount';
```
Ajoutez ensuite cette ligne à la fin de la fonction `updateState()`:
```js
localStorage.setItem(storageKey, JSON.stringify(state.account));
```
Avec cela, les données du compte utilisateur seront conservées et toujours à jour car nous avons centralisé précédemment toutes nos mises à jour détat. Cest là que nous commençons à bénéficier de tous nos refactors précédents 🙂.
Au fur et à mesure que les données sont enregistrées, nous devons également nous occuper de les restaurer lorsque lapplication est chargée. Puisque nous allons commencer à avoir plus de code dinitialisation, il peut être judicieux de créer une nouvelle fonction `init`, qui inclut également notre code précédent au bas de `app.js`:
```js
function init() {
const savedAccount = localStorage.getItem(storageKey);
if (savedAccount) {
updateState('account', JSON.parse(savedAccount));
}
// Notre précédent code d'initialisation
window.onpopstate = () => updateRoute();
updateRoute();
}
init();
```
Ici, nous récupérons les données enregistrées, et sil y en a, nous mettons à jour létat en conséquence. Il est important de le faire *avant* de mettre à jour litinéraire, car il peut y avoir du code sappuyant sur létat lors de la mise à jour de la page.
Nous pouvons également faire de la page *Dashboard* notre page par défaut de lapplication, car nous conservons maintenant les données du compte. Si aucune donnée nest trouvée, le tableau de bord se charge de rediriger vers la page *Login* de toute façon. Dans `updateRoute()`, remplacez le secours `return navigate('/login');` par `return navigate('/dashboard');`.
Maintenant, connectez-vous à lapplication et essayez dactualiser la page. Vous devez rester sur le tableau de bord. Avec cette mise à jour, nous avons résolu tous nos problèmes initiaux...
## Actualiser les données
... Mais nous pourrions aussi en avoir créé un nouveau. Oups!
Accédez au tableau de bord à laide du compte `test`, puis exécutez cette commande sur un terminal pour créer une nouvelle transaction:
```sh
curl --request POST \
--header "Content-Type: application/json" \
--data "{ \"date\": \"2020-07-24\", \"object\": \"Bought book\", \"amount\": -20 }" \
http://localhost:5000/api/accounts/test/transactions
```
Essayez dactualiser la page du tableau de bord dans le navigateur maintenant. Que se passe-t-il? Voyez-vous la nouvelle transaction?
Létat est conservé indéfiniment grâce au `localStorage`, mais cela signifie également quil nest jamais mis à jour tant que vous ne vous déconnectez pas de lapplication et que vous ne vous connectez pas à nouveau!
Une stratégie possible pour résoudre ce problème consiste à recharger les données du compte chaque fois que le tableau de bord est chargé, afin déviter les données de blocage.
### Tâche
Créez une nouvelle fonction `updateAccountData`:
```js
async function updateAccountData() {
const account = state.account;
if (!account) {
return logout();
}
const data = await getAccount(account.user);
if (data.error) {
return logout();
}
updateState('account', data);
}
```
Cette méthode vérifie que nous sommes actuellement connectés puis recharge les données du compte à partir du serveur.
Créez une autre fonction nommée `refresh`:
```js
async function refresh() {
await updateAccountData();
updateDashboard();
}
```
Celui-ci met à jour les données du compte, puis se charge de mettre à jour le code HTML de la page du tableau de bord. Cest ce que nous devons appeler lorsque litinéraire du tableau de bord est chargé. Mettez à jour la définition ditinéraire avec:
```js
const routes = {
'/login': { templateId: 'login' },
'/dashboard': { templateId: 'dashboard', init: refresh }
};
```
Essayez de recharger le tableau de bord maintenant, il devrait afficher les données de compte mises à jour.
---
## 🚀 Challenge
Maintenant que nous rechargeons les données du compte chaque fois que le tableau de bord est chargé, pensez-vous que nous devons encore conserver *toutes les données du compte*?
Essayez de travailler ensemble pour modifier ce qui est enregistré et chargé à partir de `localStorage` pour ninclure que ce qui est absolument nécessaire pour que lapplication fonctionne.
## Quiz de validation des connaissances
[Quiz de validation des connaissances](https://calm-wave-0d1a32b03.1.azurestaticapps.net/quiz/48?loc=fr)
## Affectation
[Implémenter la boîte de dialogue "Ajouter une transaction"](assignment.fr.md)
Voici un exemple de résultat après avoir terminé laffectation:
![Capture décran montrant un exemple de boîte de dialogue "Ajouter une transaction"](../images/dialog.png)