Update Translations

This commit is contained in:
Lee Stott
2025-08-23 20:47:28 +01:00
committed by GitHub
parent eff26d962b
commit 4c689557b5
497 changed files with 0 additions and 50930 deletions

View File

@@ -1,278 +0,0 @@
# Conceptos de gestión del estado
## [Pre-lecture prueba](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/47)
### Introducción
A medida que una aplicación web crece, mantener un seguimiento limpio de todos los flujos de datos se convierte en un desafío. Qué código obtiene los datos, qué página los consume, dónde y cuándo debe actualizarse, es fácil terminar con un código desordenado y difícil de mantener. Esto es especialmente cierto cuando necesita compartir datos entre diferentes páginas de su aplicación, por ejemplo, los datos del usuario. El concepto de *gestión estatal* siempre ha existido en todo tipo de programas, pero a medida que las aplicaciones web siguen creciendo en complejidad, ahora es un punto clave en el que pensar durante el desarrollo.
En esta parte final, revisaremos la aplicación que creamos para repensar cómo se administra el estado, lo que permite la compatibilidad con la actualización del navegador en cualquier momento y la persistencia de datos en las sesiones de los usuarios.
### Requisito previo
Debe haber completado la parte [obtención de datos](../3-data/README.md) de la aplicación web para esta lección. También necesita instalar [Node.js](https://nodejs.org) y [ejecutar la API del servidor](../api/README.md) localmente para poder administrar los datos de la cuenta.
Puede probar que el servidor está funcionando correctamente ejecutando este comando en una terminal:
```sh
curl http://localhost:5000/api
# -> debe devolver "API bancaria v1.0.0" como resultado
```
---
## Repensar la gestión del estado
En la [lección anterior](../3-data/README.md), empezamos con un concepto básico de estado en nuestra aplicación con la variable global `account` que contiene los datos bancarios del usuario actualmente conectado. Sin embargo, nuestra implementación actual tiene algunas fallas. Intente actualizar la página cuando esté en el panel. Pero ¿qué pasa?
Hay 3 problemas con el código actual:
- El estado no persiste, ya que una actualización del navegador lo lleva de regreso a la página de inicio de sesión.
- Hay múltiples funciones que modifican el estado. A medida que la aplicación crece, puede dificultar el seguimiento de los cambios y es fácil olvidarse de actualizar uno.
- El estado no se limpia, cuando hace clic en *Cerrar sesión*, los datos de la cuenta siguen ahí aunque esté en la página de inicio de sesión.
Podríamos actualizar nuestro código para abordar estos problemas uno por uno, pero crearía más duplicación de código y haría que la aplicación sea más compleja y difícil de mantener. O podríamos hacer una pausa por unos minutos y repensar nuestra estrategia.
> ¿Qué problemas estamos realmente tratando de resolver aquí?
La [gestión del estado](https://en.wikipedia.org/wiki/State_management) se trata de encontrar un buen enfoque para resolver estos dos problemas particulares:
- ¿Cómo mantener los flujos de datos en una aplicación fáciles de entender?
- ¿Cómo mantener los datos de estado siempre sincronizados con la interfaz de usuario (y viceversa)?
Una vez que se haya ocupado de esto, es posible que cualquier otro problema que pueda tener ya esté solucionado o que sea más fácil de solucionar. Hay muchos enfoques posibles para resolver estos problemas, pero optaremos por una solución común que consiste en **centralizar los datos y las formas de cambiarlos**. Los flujos de datos serían así:
![](./images/data-flow.png)
> No cubriremos aquí la parte en la que los datos activan automáticamente la actualización de la vista, ya que está vinculada a conceptos más avanzados de [Programación reactiva](https://en.wikipedia.org/wiki/Reactive_programming). Es un buen tema de seguimiento si estás preparado para una inmersión profunda.
✅ Hay muchas bibliotecas con diferentes enfoques para la administración del estado, [Redux](https://redux.js.org) es una opción popular. Eche un vistazo a los conceptos y patrones utilizados, ya que a menudo es una buena manera de aprender qué problemas potenciales puede enfrentar en aplicaciones web grandes y cómo se pueden resolver.
### Tarea
Comenzaremos con un poco de refactorización. Reemplace la declaración `account`:
```js
let account = null;
```
Con:
```js
let state = {
account: null
};
```
La idea es *centralizar* todos los datos de nuestra aplicación en un solo objeto de estado. Solo tenemos "cuenta" por ahora en el estado, por lo que no cambia mucho, pero crea un camino fácil para las evoluciones.
También tenemos que actualizar las funciones usándolo. En las funciones `register()` y `login()`, reemplace `account=...` con `state.account=...`;
En la parte superior de la función `updateDashboard()`, agregue esta línea:
```js
const account = state.account;
```
Esta refactorización por sí sola no trajo muchas mejoras, pero la idea era sentar las bases para los próximos cambios.
## Seguimiento de cambios de datos
Ahora que hemos implementado el objeto `state` para almacenar nuestros datos, el siguiente paso es centralizar las actualizaciones. El objetivo es facilitar el seguimiento de los cambios y cuándo ocurren.
Para evitar que se realicen cambios en el objeto `state`, también es una buena práctica considerarlo [*inmutable*](https://en.wikipedia.org/wiki/Immutable_object), lo que significa que no se puede modificar en absoluto. También significa que debe crear un nuevo objeto de estado si desea cambiar algo en él. Al hacer esto, crea una protección contra [efectos secundarios](https://en.wikipedia.org/wiki/Side_effect_(computer_science)) potencialmente no deseados y abre posibilidades para nuevas funciones en su aplicación, como implementar deshacer / rehacer, al mismo tiempo que facilita la depuración. Por ejemplo, puede registrar todos los cambios realizados en el estado y mantener un historial de los cambios para comprender el origen de un error.
En JavaScript, puede usar [`Object.freeze()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) para crear una versión inmutable de un objeto. Si intenta realizar cambios en un objeto inmutable, se generará una excepción.
✅ ¿Conoce la diferencia entre un objeto *superficial* y un objeto inmutable *profundo*? Puede leer sobre esto [aquí](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze#What_is_shallow_freeze).
### Tarea
Creemos una nueva función `updateState()`:
```js
function updateState(property, newData) {
state = Object.freeze({
...state,
[property]: newData
});
}
```
En esta función, estamos creando un nuevo objeto de estado y copiamos datos del estado anterior usando el operador [*spread (`...`)*](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Spread_in_object_literals). Luego anulamos una propiedad particular del objeto de estado con los nuevos datos usando la [notación de corchetes](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Working_with_Objects#Objects_and_properties) `[property]` para asignación. Finalmente, bloqueamos el objeto para evitar modificaciones usando `Object.freeze()`. Solo tenemos la propiedad `account` almacenada en el estado por ahora, pero con este enfoque puede agregar tantas propiedades como necesite en el estado.
También actualizaremos la inicialización del `estado` para asegurarnos de que el estado inicial también esté congelado:
```js
let state = Object.freeze({
account: null
});
```
Después de eso, actualice la función `register` reemplazando la asignación `state.account = result;` con:
```js
updateState('account', result);
```
Haz lo mismo con la función `login`, reemplazando `state.account = data;` con:
```js
updateState('account', data);
```
Ahora aprovecharemos la oportunidad para solucionar el problema de que los datos de la cuenta no se borran cuando el usuario hace clic en *Cerrar sesión*.
Cree una nueva función `logout()`:
```js
function logout() {
updateState('account', null);
navigate('/login');
}
```
En `updateDashboard()`, reemplace la redirección `return navigate('/login');` por `return logout()`;
Intente registrar una nueva cuenta, cierre la sesión y vuelva a iniciarla para comprobar que todo sigue funcionando correctamente.
> Consejo: puede echar un vistazo a todos los cambios de estado agregando `console.log(state)` en la parte inferior de `updateState()` y abriendo la consola en las herramientas de desarrollo de su navegador.
## Persiste el estado
La mayoría de las aplicaciones web necesitan conservar los datos para poder funcionar correctamente. Todos los datos críticos generalmente se almacenan en una base de datos y se accede a ellos a través de una API de servidor, como los datos de la cuenta de usuario en nuestro caso. Pero a veces, también es interesante conservar algunos datos en la aplicación cliente que se ejecuta en su navegador, para una mejor experiencia de usuario o para mejorar el rendimiento de carga.
Cuando desee conservar los datos en su navegador, hay algunas preguntas importantes que debe hacerse:
- *¿Son los datos confidenciales?* Debe evitar almacenar datos confidenciales en el cliente, como contraseñas de usuario.
- *¿Por cuánto tiempo necesita conservar estos datos?* ¿Planea acceder a estos datos solo para la sesión actual o desea que se almacenen para siempre?
Hay varias formas de almacenar información dentro de una aplicación web, dependiendo de lo que desee lograr. Por ejemplo, puede utilizar las URL para almacenar una consulta de búsqueda y hacer que se pueda compartir entre los usuarios. También puede utilizar [cookies HTTP](https://developer.mozilla.org/docs/Web/HTTP/Cookies) si los datos deben compartirse con el servidor, como [autenticación](https://en.wikipedia.org/wiki/Authentication) de la información.
Otra opción es utilizar una de las muchas API del navegador para almacenar datos. Dos de ellos son particularmente interesantes:
- [`localStorage`](https://developer.mozilla.org/docs/Web/API/Window/localStorage): un [almacén de claves / valores](https://en.wikipedia.org/wiki/Key%E2%80%93value_database) que permite conservar datos específicos del sitio web actual en diferentes sesiones. Los datos guardados en él nunca caducan.
- [`sessionStorage`](https://developer.mozilla.org/docs/Web/API/Window/sessionStorage): este funciona igual que `localStorage` excepto que los datos almacenados en él se borran cuando finaliza la sesión (cuando se cierra el navegador).
Tenga en cuenta que estas dos API solo permiten almacenar [cadenas](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String). Si desea almacenar objetos complejos, deberá serializarlos al formato [JSON](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON) usando [`JSON.stringify()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify).
✅ Si desea crear una aplicación web que no funcione con un servidor, también es posible crear una base de datos en el cliente usando la [API de IndexedDB](https://developer.mozilla.org/docs/Web/API/IndexedDB_API). Este está reservado para casos de uso avanzados o si necesita almacenar una cantidad significativa de datos, ya que es más complejo de usar.
### Tarea
Queremos que nuestros usuarios permanezcan conectados hasta que hagan clic explícitamente en el botón *Cerrar sesión*, por lo que usaremos `localStorage` para almacenar los datos de la cuenta. Primero, definamos una clave que usaremos para almacenar nuestros datos.
```js
const storageKey = 'savedAccount';
```
Luego agregue esta línea al final de la función `updateState()`:
```js
localStorage.setItem(storageKey, JSON.stringify(state.account));
```
Con esto, los datos de la cuenta de usuario serán persistentes y siempre actualizados ya que centralizamos previamente todas nuestras actualizaciones de estado. Aquí es donde comenzamos a beneficiarnos de todas nuestras refactorizaciones anteriores 🙂.
A medida que se guardan los datos, también tenemos que encargarnos de restaurarlos cuando se carga la aplicación. Ya que comenzaremos a tener más código de inicialización, puede ser una buena idea crear una nueva función `init`, que también incluya nuestro código anterior al final de `app.js`:
```js
function init() {
const savedAccount = localStorage.getItem(storageKey);
if (savedAccount) {
updateState('account', JSON.parse(savedAccount));
}
// Nuestro código de inicialización anterior
window.onpopstate = () => updateRoute();
updateRoute();
}
init();
```
Aquí recuperamos los datos guardados y, si hay alguno, actualizamos el estado en consecuencia. Es importante hacer esto *antes* de actualizar la ruta, ya que puede haber un código que dependa del estado durante la actualización de la página.
También podemos hacer que la página *Panel de control* sea nuestra página predeterminada de la aplicación, ya que ahora estamos conservando los datos de la cuenta. Si no se encuentran datos, el panel se encarga de redirigir a la página *Iniciar sesión* de todos modos. En `updateRoute()`, reemplace el respaldo `return navigate('/login');` con `return navigate('dashboard');`.
Ahora inicie sesión en la aplicación e intente actualizar la página, debe permanecer en el tablero. Con esa actualización nos hemos ocupado de todos nuestros problemas iniciales ...
## Actualizar los datos
... Pero también podríamos haber creado uno nuevo. Oups.
Vaya al panel de control con la cuenta `test`, luego ejecute este comando en una terminal para crear una nueva transacción:
```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
```
Intente actualizar la página del panel en el navegador ahora. ¿Qué ocurre? ¿Ves la nueva transacción?
El estado se conserva indefinidamente gracias al `localStorage`, pero eso también significa que nunca se actualiza hasta que cierre la sesión de la aplicación y vuelva a iniciarla.
Una posible estrategia para solucionarlo es volver a cargar los datos de la cuenta cada vez que se carga el panel, para evitar que se atasquen los datos.
### Tarea
Cree una nueva función `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);
}
```
Este método comprueba que estamos conectados actualmente y luego vuelve a cargar los datos de la cuenta desde el servidor.
Cree otro nombre de función `refresh`:
```js
async function refresh() {
await updateAccountData();
updateDashboard();
}
```
Éste actualiza los datos de la cuenta, luego se encarga de actualizar el HTML de la página del tablero. Es lo que necesitamos llamar cuando se carga la ruta del tablero. Actualice la definición de ruta con:
```js
const routes = {
'/login': { templateId: 'login' },
'/dashboard': { templateId: 'dashboard', init: refresh }
};
```
Intente volver a cargar el panel ahora, debería mostrar los datos de la cuenta actualizados.
---
## 🚀 Desafío
Ahora que recargamos los datos de la cuenta cada vez que se carga el panel, ¿cree que aún necesitamos conservar *todos los datos de la cuenta*?
Intente trabajar juntos para cambiar lo que se guarda y carga desde `localStorage` para incluir solo lo que es absolutamente necesario para que la aplicación funcione.
## [Post-lecture prueba](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/48)
## Asignación
[Implementar el cuadro de diálogo "Agregar transacción"](assignment.es.md)
Aquí hay un ejemplo de resultado después de completar la tarea:
![Captura de pantalla que muestra un ejemplo de diálogo "Agregar transacción"](../images/dialog.png)

View File

@@ -1,281 +0,0 @@
# Créer une application bancaire Partie 4: Concepts de gestion d'état
## Quiz préalable
[Quiz préalable](https://ashy-river-0debb7803.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://ashy-river-0debb7803.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)

View File

@@ -1,284 +0,0 @@
# एक बैंकिंग ऐप का निर्माण करें भाग 4: स्टेट प्रबंधन की अवधारणा
## पूर्व व्याख्यान प्रश्नोत्तरी
[पूर्व व्याख्यान प्रश्नोत्तरी](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/47?loc=hi)
### परिचय
जैसे-जैसे वेब एप्लिकेशन बढ़ता है, यह सभी डेटा फ्लो पर नज़र रखना एक चुनौती बन जाता है। किस कोड को डेटा मिलता है, कौन सा पेज इसका उपभोग करता है, कहां और कब इसे अपडेट करने की आवश्यकता होती है ... गंदे कोड को समाप्त करना आसान है जिसे बनाए रखना मुश्किल है। यह विशेष रूप से सच है जब आपको अपने ऐप के विभिन्न पृष्ठों के बीच डेटा साझा करने की आवश्यकता होती है, उदाहरण के लिए उपयोगकर्ता डेटा। *स्टेट प्रबंधन* की अवधारणा हमेशा सभी प्रकार के कार्यक्रमों में मौजूद रही है, लेकिन जैसा कि वेब ऐप जटिलता में बढ़ रहे हैं, यह अब विकास के दौरान सोचने का एक महत्वपूर्ण बिंदु है।
इस अंतिम भाग में, हम उस ऐप पर नज़र डालेंगे जिसे हमने बनाया है कि स्टेट कैसे प्रबंधित किया जाता है, किसी भी बिंदु पर ब्राउज़र रीफ्रेश के लिए समर्थन और उपयोगकर्ता सत्रों में डेटा को बनाए रखने की अनुमति देता है।
### शर्त
आपको इस पाठ के लिए वेब ऐप का [डेटा प्राप्त करने](../../3-data/translations/README.hi.md) वाला भाग पूरा करना होगा। आपको स्थानीय रूप से [Node.js](https://nodejs.org) और [सर्वर एपीआई चलाने](../../api/translations/README.hi.md) को स्थापित करने की आवश्यकता है ताकि आप खाता डेटा प्रबंधित कर सकें।
आप परीक्षण कर सकते हैं कि सर्वर टर्मिनल में इस कमांड को निष्पादित करके ठीक से चल रहा है:
```sh
curl http://localhost:5000/api
# -> should return "Bank API v1.0.0" as a result
```
---
## स्टेट प्रबंधन पुनर्विचार
[पिछले पाठ](../../3-data/translations/README.hi.md) में, हमने अपने ऐप में वैश्विक `account` चर के साथ स्टेट की एक बुनियादी अवधारणा पेश की, जिसमें वर्तमान में लॉग इन उपयोगकर्ता के लिए बैंक डेटा शामिल है। हालांकि, हमारे वर्तमान कार्यान्वयन में कुछ खामियां हैं। जब आप डैशबोर्ड पर हों तो पृष्ठ को ताज़ा करने का प्रयास करें। क्या होता है?
वर्तमान कोड के साथ 3 समस्याएँ हैं:
- स्टेट कायम नहीं है, क्योंकि ब्राउज़र रीफ़्रेश आपको लॉगिन पृष्ठ पर वापस ले जाता है।
- स्टेट को संशोधित करने वाले कई कार्य हैं। जैसे-जैसे ऐप बढ़ता है, यह परिवर्तनों को ट्रैक करना मुश्किल बना सकता है और किसी एक को अपडेट करना भूल जाना आसान है।
- स्टेट को साफ नहीं किया जाता है, इसलिए जब आप * लॉगआउट * पर क्लिक करते हैं, तो खाता डेटा अभी भी वहीं है जबकि आप लॉगिन पेज पर हैं।
हम एक-एक करके इन मुद्दों से निपटने के लिए अपने कोड को अपडेट कर सकते हैं, लेकिन यह अधिक कोड दोहराव पैदा करेगा और ऐप को अधिक जटिल और बनाए रखना मुश्किल होगा। या हम कुछ मिनटों के लिए रुक सकते हैं और अपनी रणनीति पर फिर से विचार कर सकते हैं।
>
हम वास्तव में किन समस्याओं को हल करने की कोशिश कर रहे हैं?
[स्टेट प्रबंधन](https://en.wikipedia.org/wiki/State_management) इन दो विशेष समस्याओं को हल करने के लिए एक अच्छा तरीका खोजने के बारे में है:
- ऐप में डेटा फ्लो को कैसे समझा जा सकता है?
- उपयोगकर्ता इंटरफ़ेस (और इसके विपरीत) के साथ स्टेट के डेटा को हमेशा सिंक में कैसे रखा जाए?
एक बार जब आप इनका ध्यान रख लेते हैं, तो हो सकता है कि कोई अन्य समस्या या तो पहले से ही ठीक हो जाए या जिसे ठीक करना आसान हो जाए। इन समस्याओं को हल करने के लिए कई संभावित दृष्टिकोण हैं, लेकिन हम एक सामान्य समाधान के साथ जाएंगे जिसमें डेटा को **केंद्रीकृत करना और इसे बदलने के तरीके** शामिल हैं। डेटा प्रवाह इस तरह होगा:
![HTML, उपयोगकर्ता क्रियाओं और स्टेट के बीच डेटा प्रवाह दिखाती हुई स्कीमा](../images/data-flow.png)
> हम यहां उस हिस्से को कवर नहीं करेंगे जहां डेटा स्वचालित रूप से दृश्य अद्यतन को ट्रिगर करता है, क्योंकि यह [रीऐक्टिव प्रोग्रामिंग](https://en.wikipedia.org/wiki/Reactive_programming) की अधिक उन्नत अवधारणाओं से बंधा है। यदि आप एक गहरी गोता लगाने के लिए एक अच्छा अनुवर्ती विषय है।
✅ स्टेट प्रबंधन के विभिन्न दृष्टिकोणों के साथ वहाँ बहुत सारे पुस्तकालय हैं, [Redux](https://redux.js.org) एक लोकप्रिय विकल्प है। उपयोग की जाने वाली अवधारणाओं और पैटर्नों पर एक नज़र डालें क्योंकि यह अक्सर सीखने का एक अच्छा तरीका है कि आप बड़े वेब ऐप में किन संभावित मुद्दों का सामना कर रहे हैं और इसे कैसे हल किया जा सकता है।
### टास्क
हम थोड़ा सा रिफैक्टरिंग के साथ शुरुआत करेंगे। `account` घोषणा बदलें:
```js
let account = null;
```
With:
```js
let state = {
account: null
};
```
एक स्टेट वस्तु में हमारे सभी एप्लिकेशन डेटा को *केंद्रीकृत* करने का विचार है। हमारे पास स्टेट में अभी के लिए `account` है, इसलिए यह बहुत अधिक नहीं बदलता है, लेकिन यह प्रस्तावों के लिए एक रास्ता बनाता है।
हमें इसका उपयोग करके कार्यों को भी अपडेट करना होगा। `register()` और `login()` फंगक्शनसमे,`account = ...` को `state.account = ...` से बदले;
`UpdateDashboard()` फ़ंक्शन के शीर्ष पर, यह पंक्ति जोड़ें:
```js
const account = state.account;
```
अपने आप में इस रिफ्रैक्टिंग में बहुत सुधार नहीं हुआ, लेकिन विचार अगले बदलावों की नींव रखने का था।
## डेटा परिवर्तन ट्रैक करें
अब जब हमने अपने डेटा को स्टोर करने के लिए `state` ऑब्जेक्ट को रखा है, तो अगला चरण अपडेट को केंद्रीकृत करना है। लक्ष्य किसी भी परिवर्तन का ट्रैक रखना आसान है और जब वे होते हैं।
`state` वस्तु में किए गए परिवर्तनों से बचने के लिए, यह [*अपरिवर्तनीय*](https://en.wikipedia.org/wiki/Immutable_object) पर विचार करने के लिए एक अच्छा अभ्यास है, जिसका अर्थ है कि इसे बिल्कुल भी संशोधित नहीं किया जा सकता है। इसका अर्थ यह भी है कि यदि आप इसमें कुछ भी बदलना चाहते हैं तो आपको एक नया स्टेट ऑब्जेक्ट बनाना होगा। ऐसा करने से, आप संभावित रूप से अवांछित [साइड इफेक्ट्स](https://en.wikipedia.org/wiki/Side_effect_(computer_science)) के बारे में एक सुरक्षा का निर्माण करते हैं, और अपने ऐप में नई सुविधाओं के लिए संभावनाएं खोलते हैं जैसे कि undo/redo को लागू करना, जबकि डिबग करना भी आसान है। उदाहरण के लिए, आप स्टेट में किए गए प्रत्येक परिवर्तन को लॉग कर सकते हैं और बग के स्रोत को समझने के लिए परिवर्तनों का इतिहास रख सकते हैं।
जावास्क्रिप्ट में, आप एक अपरिवर्तनीय संस्करण एक ऑब्जेक्ट बनाने के लिए [`Object.freeze()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) का उपयोग कर सकते हैं । यदि आप एक अपरिवर्तनीय वस्तु में परिवर्तन करने की कोशिश करते हैं, तो एक अपवाद उठाया जाएगा।
✅ क्या आप एक *उथले* और एक *गहरी* अपरिवर्तनीय वस्तु के बीच का अंतर जानते हैं? आप इसके बारे में [यहां](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze#What_is_shallow_freeze) पढ़ सकते हैं.
### टास्क
Let's create a new `updateState()` function:
```js
function updateState(property, newData) {
state = Object.freeze({
...state,
[property]: newData
});
}
```
इस फ़ंक्शन में, हम [*spread (`...`) operator*](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Spread_in_object_literals) का उपयोग करके पिछले स्टेट से एक नया स्टेट ऑब्जेक्ट और कॉपी डेटा बना रहे हैं। फिर हम नए डेटा के साथ स्टेट ऑब्जेक्ट की एक विशेष प्रॉपर्टी को ओवरराइड करते हैं [ब्रैकेट नोटेशन] `[property]` असाइनमेंट के लिए। अंत में, हम `Object.freeze()` का उपयोग करके संशोधनों को रोकने के लिए ऑब्जेक्ट को लॉक करते हैं। हमारे पास अब केवल स्टेट में संग्रहीत `अकाउंट` प्रॉपर्टी है, लेकिन इस दृष्टिकोण के साथ आप स्टेट में जितनी आवश्यकता हो उतने गुण जोड़ सकते हैं।
हम यह भी सुनिश्चित करेंगे कि प्रारंभिक अवस्था भी जम गई है, यह सुनिश्चित करने के लिए `state` आरंभीकरण को अद्यतन करेगा:
```js
let state = Object.freeze({
account: null
});
```
उसके बाद, `state.account = result` के स्थान पर `register` फ़ंक्शन को अपडेट करें; असाइनमेंट के साथ:
```js
updateState('account', result);
```
`login` फ़ंक्शन के साथ भी ऐसा ही करें, `state.account = data;` के स्थान पर:
```js
updateState('account', data);
```
जब उपयोगकर्ता *लॉगआउट* पर क्लिक करेगा तो हम खाता डेटा के मुद्दे को ठीक करने का मौका नहीं लेंगे।
एक नया `logout()` फंगक्शन बनाए:
```js
function logout() {
updateState('account', null);
navigate('/login');
}
```
`updateDashboard()` मे , पुनर्निर्देशन `return navigate('/login');` की जगह `return logout()` के साथ;
एक नया खाता पंजीकृत करने की कोशिश करें, लॉग आउट करें और फिर से जाँच करें कि सब कुछ अभी भी सही ढंग से काम करता है।
युक्ति: आप `updateState()` के तल पर `console.log(state)` जोड़कर और अपने ब्राउज़र के डेवलपमेंट टूल में कंसोल खोलकर सभी स्टेट परिवर्तनों पर एक नज़र डाल सकते हैं।
## स्टेट को पर्सिस्ट करे
अधिकांश वेब ऐप्स को डेटा को सही ढंग से काम करने में सक्षम बनाने के लिए लगातार बने रहने की आवश्यकता होती है। सभी महत्वपूर्ण डेटा को आमतौर पर डेटाबेस पर संग्रहीत किया जाता है और सर्वर एपीआई के माध्यम से एक्सेस किया जाता है, जैसे हमारे मामले में उपयोगकर्ता खाता डेटा। लेकिन कभी-कभी, एक बेहतर उपयोगकर्ता अनुभव के लिए या लोडिंग प्रदर्शन में सुधार करने के लिए आपके ब्राउज़र में चल रहे क्लाइंट ऐप पर कुछ डेटा को जारी रखना भी दिलचस्प है।
जब आप अपने ब्राउज़र में डेटा को पर्सिस्ट रखना चाहते हैं, तो कुछ महत्वपूर्ण सवाल हैं जो आपको खुद से पूछना चाहिए:
- *क्या डेटा संवेदनशील है?* आपको क्लाइंट पर किसी भी संवेदनशील डेटा, जैसे उपयोगकर्ता पासवर्ड को संग्रहीत करने से बचना चाहिए।
- *आपको यह डेटा कब तक रखने की आवश्यकता है?* क्या आप इस डेटा को केवल वर्तमान सत्र के लिए एक्सेस करने की योजना बना रहे हैं या क्या आप चाहते हैं कि यह हमेशा के लिए स्टोर हो जाए?
वेब ऐप के अंदर जानकारी संग्रहीत करने के कई तरीके हैं, जो इस बात पर निर्भर करता है कि आप क्या हासिल करना चाहते हैं। उदाहरण के लिए, आप खोज क्वेरी को संग्रहीत करने के लिए URL का उपयोग कर सकते हैं, और इसे उपयोगकर्ताओं के बीच साझा करने योग्य बना सकते हैं। यदि आप डेटा को सर्वर के साथ साझा करने की आवश्यकता है, जैसे कि [authentication](https://en.wikipedia.org/wiki/Authentication) की जानकारी।
एक अन्य विकल्प डेटा भंडारण के लिए कई ब्राउज़र एपीआई में से एक का उपयोग करना है। उनमें से दो विशेष रूप से दिलचस्प हैं:
- [`localStorage`](https://developer.mozilla.org/docs/Web/API/Window/localStorage): एक [Key/Value store](https://en.wikipedia.org/wiki/Key%E2%80%93value_database) विभिन्न सत्रों में वर्तमान वेब साइट के लिए विशिष्ट डेटा को बनाए रखने की अनुमति देते हैं। इसमें सहेजा गया डेटा कभी समाप्त नहीं होता है।
- [`sessionStorage`](https://developer.mozilla.org/docs/Web/API/Window/sessionStorage): यह एक `sessionStorage` की तरह ही काम करता है, सिवाय इसके कि इसमें संग्रहीत डेटा सत्र समाप्त होने पर (जब ब्राउज़र बंद हो जाता है) साफ हो जाता है।
यदि आप जटिल वस्तुओं को संग्रहीत करना चाहते हैं, तो आपको इसे [JSON](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON) प्रारूप [`JSON.stringify()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) का उपयोग करके क्रमबद्ध करना होगा।
✅ यदि आप एक वेब ऐप बनाना चाहते हैं जो सर्वर के साथ काम नहीं करता है, तो [`IndexedDB` API](https://developer.mozilla.org/docs/Web/API/IndexedDB_API) का उपयोग करके क्लाइंट पर डेटाबेस बनाना भी संभव है। यह एक उन्नत उपयोग मामलों के लिए आरक्षित है या यदि आपको महत्वपूर्ण मात्रा में डेटा संग्रहीत करने की आवश्यकता है, क्योंकि यह उपयोग करने के लिए अधिक जटिल है।
### टास्क
हम चाहते हैं कि हमारे उपयोगकर्ता तब तक लॉग इन रहें जब तक कि वे स्पष्ट रूप से *लॉगआउट* बटन पर क्लिक न करें, इसलिए हम खाता डेटा संग्रहीत करने के लिए `localStorage` का उपयोग करेंगे। सबसे पहले, एक कुंजी परिभाषित करते हैं जिसका उपयोग हम अपने डेटा को संग्रहीत करने के लिए करेंगे।.
```js
const storageKey = 'savedAccount';
```
फिर इस लाइन को `updateState()` फ़ंक्शन के अंत में जोड़ें:
```js
localStorage.setItem(storageKey, JSON.stringify(state.account));
```
इसके साथ, उपयोगकर्ता खाते के डेटा को बनाए रखा जाएगा और हमेशा अप-टू-डेट रहेगा क्योंकि हमने अपने सभी स्टेट अपडेट पहले केंद्रीकृत किए थे। यह वह जगह है जहाँ हम अपने सभी पिछले रिफ्लेक्टरों से लाभान्वित होने लगते हैं 🙂.
डेटा सहेजे जाने के साथ, हमें ऐप को लोड करने पर इसे पुनर्स्थापित करने का भी ध्यान रखना होगा। चूंकि हम अधिक आरंभीकरण कोड शुरू करेंगे, इसलिए यह एक नया `init` फ़ंक्शन बनाने के लिए एक अच्छा विचार हो सकता है, जिसमें `app.js` के नीचे हमारा पिछला कोड भी शामिल है।:
```js
function init() {
const savedAccount = localStorage.getItem(storageKey);
if (savedAccount) {
updateState('account', JSON.parse(savedAccount));
}
// Our previous initialization code
window.onpopstate = () => updateRoute();
updateRoute();
}
init();
```
यहां हम सहेजे गए डेटा को पुनर्प्राप्त करते हैं, और यदि कोई है तो हम तदनुसार स्टेट को अपडेट करते हैं। रूट अपडेट करने से *पहले* इसे करना महत्वपूर्ण है, क्योंकि पेज अपडेट के दौरान स्टेट पर कोड निर्भर हो सकता है।
हम अपने एप्लिकेशन डिफॉल्ट पेज को भी * डैशबोर्ड * पेज बना सकते हैं, क्योंकि हम अब खाता डेटा को जारी रख रहे हैं। यदि कोई डेटा नहीं मिला है, तो डैशबोर्ड * लॉगिन * पेज वैसे भी पुनर्निर्देशित करने का ख्याल रखता है। `updateRoute()` में, फ़ॉलबैक `return navigate('/login');` को `return navigate('/dashboard');` से बदलें।
अब ऐप में लॉगइन करें और पेज को रिफ्रेश करने की कोशिश करें। आपको डैशबोर्ड पर रहना चाहिए। उस अपडेट के साथ हमने अपने सभी शुरुआती मुद्दों का ध्यान रखा है ...
## डाटाको रिफ्रेश करे
...लेकिन हम एक नया भी बना सकते हैं। ऊपस!
`test` खाते का उपयोग करके डैशबोर्ड पर जाएं, फिर एक नया लेनदेन बनाने के लिए इस कमांड को टर्मिनल पर चलाएं:
```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
```
अब ब्राउज़र में अपने डैशबोर्ड पृष्ठ को रिफ्रेश करने का प्रयास करें। क्या होता है? क्या आप नया लेनदेन देखते हैं?
स्टेट अनिश्चित काल तक `localStorage` की बदौलत कायम है, लेकिन इसका मतलब यह भी है कि यह तब तक अपडेट नहीं किया जाता जब तक आप ऐप से लॉग-आउट नहीं करते और फिर से लॉग इन नहीं करते!
स्टेक डेटा से बचने के लिए, हर बार डैशबोर्ड को लोड करने के बाद खाते के डेटा को फिर से लोड करना एक निश्चित रणनीति है।
### टास्क
एक नया फंगक्शन `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);
}
```
यह विधि जांचती है कि हम वर्तमान में लॉग इन हैं फिर सर्वर से खाता डेटा पुनः लोड करता है।
एक दूसरा नया `refresh` नामका फंगक्शन बनाए:
```js
async function refresh() {
await updateAccountData();
updateDashboard();
}
```
यह एक खाता डेटा अपडेट करता है, फिर डैशबोर्ड पृष्ठ के HTML को अपडेट करने का ध्यान रखता है। डैशबोर्ड मार्ग लोड होने पर हमें यह कॉल करना होगा। इसके साथ रूट की परिभाषा को अपडेट करें:
```js
const routes = {
'/login': { templateId: 'login' },
'/dashboard': { templateId: 'dashboard', init: refresh }
};
```
डैशबोर्ड को अब पुनः लोड करने का प्रयास करें, इसे अद्यतन खाता डेटा प्रदर्शित करना चाहिए।
---
## 🚀 चुनौती
अब जब हम डैशबोर्ड लोड होने के बाद हर बार खाते के डेटा को लोड करते हैं, तो क्या आपको लगता है कि हमें अभी भी *सभी खाते* डेटा को बनाए रखने की आवश्यकता है?
सहेजने और लोड करने के लिए एक साथ काम करने का प्रयास करें `localStorage` से केवल उस ऐप में काम करने के लिए जो आवश्यक है उसे शामिल करें।
## व्याख्यान उपरांत प्रश्नोत्तरी
[व्याख्यान उपरांत प्रश्नोत्तरी](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/48?loc=hi)
## असाइनमेंट
["ट्रैन्सैक्शन जोड़े" डायलॉग इम्प्लमेन्ट करे](assignment.hi.md)
यहां असाइनमेंट पूरा करने के बाद एक उदाहरण दिया गया है:
![एक स्क्रीनशॉट "लेनदेन जोड़ें" डायलॉग दिखाते हुए](../images/dialog.png)

View File

@@ -1,281 +0,0 @@
# Creare un'App Bancaria Parte 4: Concetti di Gestione dello Stato
## Quiz Pre-Lezione
[Quiz Pre-Lezione](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/47?loc=it)
### Introduzione
Man mano che un'applicazione web cresce, diventa una sfida tenere traccia di tutti i flussi di dati. Quale codice riceve i dati, quale pagina li consuma, dove e quando deve essere aggiornata ... è facile ritrovarsi con codice disordinato e difficile da mantenere. Ciò è particolarmente vero quando è necessario condividere dati tra diverse pagine della propria app, ad esempio i dati dell'utente. Il concetto di *gestione dello stato* è sempre esistito in tutti i tipi di programmi, ma poiché le app web continuano a crescere in complessità, ora è un punto chiave su cui riflettere durante lo sviluppo.
In questa parte finale, si esaminerà l'app creata per ripensare a come viene gestito lo stato, consentendo il supporto per l'aggiornamento del browser in qualsiasi momento e persistendo i dati tra le sessioni utente.
### Prerequisito
È necessario aver completato la parte di [recupero dei dati](../../3-data/translations/README.it.md) dell'app web per questa lezione. È inoltre necessario installare [Node.js](https://nodejs.org) ed [eseguire l'API del server](../../api/translations/README.it.md) in locale in modo da ottenere i dati dell'account.
Si può verificare che il server funzioni correttamente eseguendo questo comando in un terminale:
```sh
curl http://localhost:5000/api
# -> dovrebbe restituire "Bank API v1.0.0" come risultato
```
---
## Ripensare la gestione dello stato
Nella [lezione precedente](../../3-data/translations/README.it.md), è stato introdotto un concetto basico di stato nell'app con la variabile globale `account` che contiene i dati bancari per l'utente attualmente connesso. Tuttavia, l'attuale implementazione presenta alcuni difetti. Si provi ad aggiornare la pagina quando ci si trova nella pagina del cruscotto. Che cosa accade?
Ci sono 3 problemi con il codice corrente:
- Lo stato non è persistente, poiché un aggiornamento del browser riporta alla pagina di accesso.
- Esistono più funzioni che modificano lo stato. Man mano che l'app cresce, può essere difficile tenere traccia delle modifiche ed è facile dimenticare di aggiornarne una.
- Lo stato non viene cancellato, quando si fa clic su *Logout* i dati dell'account sono ancora lì anche se si è nella pagina di accesso.
Si potrebbe aggiornare il codice per affrontare questi problemi uno per uno, ma creerebbe più duplicazioni del codice e renderebbe l'app più complessa e difficile da mantenere. Oppure ci si potrebbe fermare per qualche minuto e ripensare alla strategia.
> Quali problemi si stanno davvero cercando di risolvere qui?
La [gestione dello stato](https://en.wikipedia.org/wiki/State_management) consiste nel trovare un buon approccio per risolvere questi due problemi particolari:
- Come mantenere comprensibile il flusso di dati in un'app?
- Come mantenere i dati di stato sempre sincronizzati con l'interfaccia utente (e viceversa)?
Una volta che ci si è preso cura di questi, qualsiasi altro problema che si potrebbe avere potrebbe essere già stato risolto o essere diventato più facile da sistemare. Ci sono molti possibili approcci per risolvere questi problemi, ma si andrà con una soluzione comune che consiste nel **centralizzare i dati e le modalità per cambiarli**. Il flusso di dati andrebbe così:
![Schema che mostra i flussi di dati tra HTML, azioni dell'utente e stato](../images/data-flow.png)
> Non verrà trattata qui la parte in cui i dati attivano automaticamente l'aggiornamento della vista, poiché è legato a concetti più avanzati di [programmazione reattiva](https://en.wikipedia.org/wiki/Reactive_programming). È un buon argomento da sviluppare successivamente se si è pronti per un'immersione profonda.
✅ Esistono molte librerie con approcci diversi alla gestione dello stato, [Redux](https://redux.js.org) è un'opzione popolare. Dare un'occhiata ai concetti e ai modelli utilizzati spesso è un buon modo per apprendere quali potenziali problemi si potrebbe dover affrontare nelle grandi app web e come risolverli.
### Attività
Si inizierà con un po' di refattorizzazione. Sostituire la dichiarazione di `account` :
```js
let account = null;
```
Con:
```js
let state = {
account: null
};
```
L'idea è *centralizzare* tutti i dati dell'app in un unico oggetto di stato. Per ora c'è solo `account` nello stato, quindi non cambia molto, ma crea un percorso per le evoluzioni.
Si devono anche aggiornare le funzioni che lo utilizzano. Nelle funzioni `register()` e `login()` , sostituire `account = ...` con `state.account = ...`;
Nella parte superiore della funzione `updateDashboard()`, aggiungere questa riga:
```js
const account = state.account;
```
Questa refattorizzazione di per sé non ha portato molti miglioramenti, ma l'idea era di gettare le basi per i prossimi cambiamenti.
## Tenere traccia delle modifiche ai dati
Ora che si è impostato l'oggetto `state` per memorizzare i dati, il passaggio successivo è centralizzare gli aggiornamenti. L'obiettivo è rendere più facile tenere traccia di eventuali modifiche e quando si verificano.
Per evitare che vengano apportate modifiche all'oggetto `state` è anche una buona pratica considerarlo [*immutabile*](https://en.wikipedia.org/wiki/Immutable_object), nel senso che non può essere modificato affatto. Significa anche che si deve creare un nuovo oggetto di stato se si vuole cambiare qualcosa in esso. In questo modo, si crea una protezione [dagli effetti collaterali](https://it.wikipedia.org/wiki/Effetto_collaterale_(informatica)) potenzialmente indesiderati e si aprono possibilità per nuove funzionalità nella propria app come l'implementazione di funzioni di annulla/ripristina, semplificando anche il debug. Ad esempio, è possibile registrare tutte le modifiche apportate allo stato e conservare una cronologia delle modifiche per comprendere l'origine di un bug.
In JavaScript, si può utilizzare [`Object.freeze()`](https://developer.mozilla.org/it/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) per creare una versione immutabile di un oggetto. Se si prova ad apportare modifiche a un oggetto immutabile, verrà sollevata un'eccezione.
✅ Si conosce la differenza tra un oggetto *shallow* e uno *deep* immutabile? Si può leggere [qui](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze#What_is_shallow_freeze) per saperne di più.
### Attività
Creare una nuova funzione `updateState()` :
```js
function updateState(property, newData) {
state = Object.freeze({
...state,
[property]: newData
});
}
```
In questa funzione, si crea un nuovo oggetto di stato e si copiano i dati dallo stato precedente utilizzando l' [*operatore spread (`...`)*](https://developer.mozilla.org/it/docs/Web/JavaScript/Reference/Operators/Spread_syntax). Quindi si sovrascrive una particolare proprietà dell'oggetto state con i nuovi dati usando la [notazione tra parentesi quadre](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Working_with_Objects#Objects_and_properties) `[property]` per l'assegnazione. Infine, si blocca l'oggetto per impedire modifiche utilizzando `Object.freeze()`. Per ora si ha solo la proprietà `account` memorizzata nello stato, ma con questo approccio si possono aggiungere tutte le proprietà che servono nello stato.
Si aggiornerà anche l'inizializzazione di `state` per assicurarsi che anche lo stato iniziale sia congelato:
```js
let state = Object.freeze({
account: null
});
```
Successivamente, aggiornare la funzione `register` sostituendo l'istruzione `state.account = result;` con:
```js
updateState('account', result);
```
Fare lo stesso con la funzione `login` , sostituendo `state.account = data;` con:
```js
updateState('account', data);
```
Si coglie ora l'occasione per risolvere il problema della mancata cancellazione dei dati dell'account quando l'utente fa clic su *Logout*.
Creare una nuova funzione `logout ()`:
```js
function logout() {
updateState('account', null);
navigate('/login');
}
```
In `updateDashboard()`, sostituire il reindirizzamento `return navigate('/ login');` con `return logout()`;
Provare a registrare un nuovo account, a disconnettersi e ad accedere nuovamente per verificare che tutto funzioni ancora correttamente.
> Suggerimento: si può dare un'occhiata a tutti i cambiamenti di stato aggiungendo `console.log (state)` nella parte inferiore di `updateState()` e aprendo la console negli strumenti di sviluppo del browser.
## Persistere lo stato
La maggior parte delle app web deve conservare i dati per poter funzionare correttamente. Tutti i dati critici vengono solitamente archiviati su un database e accessibili tramite un'API del server, come nel nostro caso i dati dell'account utente. Ma a volte è anche interessante mantenere alcuni dati sulla parte client dell'app in esecuzione nel browser, per una migliore esperienza utente o per migliorare le prestazioni di caricamento.
Quando si vuole mantenere i dati nel browser, ci sono alcune domande importanti da porsi:
- *I dati sono sensibili?* Si dovrebbe evitare di memorizzare dati sensibili sul client, come le password degli utenti.
- *Per quanto tempo si ha bisogno di conservare questi dati?* Si prevede di accedere a questi dati solo per la sessione corrente o si desidera che vengano memorizzati per sempre?
Esistono diversi modi per archiviare le informazioni all'interno di un'app web, a seconda di ciò che si desidera ottenere. Ad esempio, si possono utilizzare gli URL per memorizzare una interrogazione di ricerca e renderla condivisibile tra gli utenti. Si possono anche utilizzare i [cookie HTTP](https://developer.mozilla.org/it/docs/Web/HTTP/Cookies) se i dati devono essere condivisi con il server, come le informazioni di [autenticazione](https://it.wikipedia.org/wiki/Autenticazione) .
Un'altra opzione è utilizzare una delle tante API del browser per la memorizzazione dei dati. Due di loro sono particolarmente interessanti:
- [`localStorage`](https://developer.mozilla.org/it/docs/Web/API/Window/localStorage): un [archivio chiave/valore](https://en.wikipedia.org/wiki/Key%E2%80%93value_database) che consente di persistere i dati specifici del sito Web corrente in diverse sessioni. I dati salvati in esso non scadono mai.
- [`sessionStorage`](https://developer.mozilla.org/it/docs/Web/API/Window/sessionStorage): funziona come `localStorage` tranne per il fatto che i dati in esso memorizzati vengono cancellati al termine della sessione (alla chiusura del browser).
Notare che entrambe queste API consentono solo di memorizzare [stringhe](https://developer.mozilla.org/it/docs/Web/JavaScript/Reference/Global_Objects/String). Se si desidera archiviare oggetti complessi, si dovranno serializzare nel formato [JSON](https://developer.mozilla.org/it/docs/Web/JavaScript/Reference/Global_Objects/JSON) utilizzando [`JSON.stringify()`](https://developer.mozilla.org/it/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify).
✅ Se si desidera creare un'app web che non funziona con un server, è anche possibile creare un database sul client utilizzando l' [`API` IndexedDB](https://developer.mozilla.org/it/docs/Web/API/IndexedDB_API). Questo è riservato a casi d'uso avanzati o se è necessario archiviare una quantità significativa di dati, poiché è più complesso da usare.
### Attività
Si vuole che gli utenti rimangano collegati fino a quando non fanno clic esplicitamente sul pulsante *Logout* , quindi si utilizzerà `localStorage` per memorizzare i dati dell'account. Per prima cosa, si definisce una chiave che verrà usata per memorizzare i dati.
```js
const storageKey = 'savedAccount';
```
Aggiungere quindi questa riga alla fine della funzione `updateState()`:
```js
localStorage.setItem(storageKey, JSON.stringify(state.account));
```
Con questo, i dati dell'account utente verranno mantenuti e sempre aggiornati poiché si sono centralizzati in precedenza tutti gli aggiornamenti di stato. È qui che si inizia a trarre vantaggio da tutte le rifattorizzazioni precedenti 🙂.
Poiché i dati vengono salvati, ci si deve anche occupare di ripristinarli quando l'app viene caricata. Dato che si inizierà ad avere più codice di inizializzazione, potrebbe essere una buona idea creare una nuova funzione di inizializzazione `init` , che includa anche il codice precedente nella parte inferiore di `app.js`:
```js
function init() {
const savedAccount = localStorage.getItem(storageKey);
if (savedAccount) {
updateState('account', JSON.parse(savedAccount));
}
// Il codice di inizializzazione precedente
window.onpopstate = () => updateRoute();
updateRoute();
}
init();
```
Qui si recuperano i dati salvati e, se ce ne sono, si aggiorna lo stato di conseguenza. È importante farlo *prima* di aggiornare la rotta, poiché potrebbe esserci del codice che si basa sullo stato durante l'aggiornamento della pagina.
Si può anche rendere la pagina del *cruscotto* la pagina predefinita dell'applicazione, poiché ora si sta persistendo i dati dell'account. Se non vengono trovati dati, il cruscotto si occupa comunque di reindirizzare alla pagina di *Login* . In `updateRoute()`, sostituire le istruzioni di contingenza `return navigate ('/login');` con `return navigate ('/dashboard') ;`.
Ora accedere all'app e provare ad aggiornare la pagina, si dovrebbe rimanere sul cruscotto. Con quell'aggiornamento ci si è presi cura di tutti i problemi iniziali...
## Aggiornare i dati
...Si potrebbe uttavia anche averne creato uno nuovo. Oops!
Andare al cruscotto utilizzando l'account `test`, quindi eseguire questo comando su un terminale per creare una nuova transazione:
```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
```
Provare subito ad aggiornare la pagina del cruscotto nel browser. Che cosa accade? Si vede la nuova transazione?
Lo stato viene mantenuto indefinitamente grazie a `localStorage`, ma ciò significa anche che non viene aggiornato fino a quando non si esce dall'app e si accede di nuovo!
Una possibile strategia per risolvere questo problema è ricaricare i dati dell'account ogni volta che viene caricato il cruscotto, per evitare lo stallo dei dati.
### Attività
Creare una nuova funzione `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);
}
```
Questo metodo controlla che si sia attualmente collegati, quindi ricarica i dati dell'account dal server.
Creare un'altra funzione chiamata `refresh`:
```js
async function refresh() {
await updateAccountData();
updateDashboard();
}
```
Questa aggiorna i dati dell'account, quindi si occupa dell'aggiornamento dell'HTML della pagina del cruscotto. È ciò che si deve chiamare quando viene caricata la rotta del cruscotto (dashboard). Aggiornare la definizione del percorso con:
```js
const routes = {
'/login': { templateId: 'login' },
'/dashboard': { templateId: 'dashboard', init: refresh }
};
```
Provare a ricaricare il cruscotto ora, dovrebbe visualizzare i dati dell'account aggiornati.
---
## 🚀 Sfida
Ora che i dati dell'account vengono ricaricati ogni volta che viene caricato il cruscotto, si pensa che sia ancora necessario persistere *tutti i dati dell'account* ?
Provare a lavorare insieme per cambiare ciò che viene salvato e caricato da `localStorage` per includere solo ciò che è assolutamente necessario per il funzionamento dell'app.
## Quiz Post-Lezione
[Quiz post-lezione](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/48?loc=it)
## Compito
[Implementare la finestra di dialogo "Aggiungi transazione"](assignment.it.md)
Ecco un esempio di risultato dopo aver completato il compito:
![Videata che mostra un esempio di dialogo "Aggiungi transazione"](../images/dialog.png)

View File

@@ -1,281 +0,0 @@
# バンキングアプリを作ろう その 4: 状態管理の概念
## レッスン前の小テスト
[レッスン前の小テスト](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/47?loc=ja)
### イントロダクション
Web アプリケーションが成長するにつれて、すべてのデータの流れを追跡することが難しくなります。どのコードがデータを取得し、どのページがデータを消費し、どこでいつ更新する必要があるのか...メンテナンスが難しい厄介なコードになってしまいがちです。これは、ユーザーデータなど、アプリの異なるページ間でデータを共有する必要がある場合に特に当てはまります。*状態管理*の概念は、あらゆる種類のプログラムに常に存在していますが、Web アプリの複雑さが増すにつれ、開発中に考えるべき重要なポイントになってきています。
この最後のパートでは、状態の管理方法を再考するために構築したアプリを見ていきます。任意の時点でのブラウザの更新をサポートし、ユーザーセッション間でのデータの永続化を可能にします。
### 前提条件
このレッスンでは、Web アプリの[データ取得](../../3-data/translations/README.ja.md)の部分が完了している必要があります。また、アカウントデータを管理するためには、ローカルに [Node.js](https://nodejs.org/ja) をインストールし、[サーバー API を実行する](../../api/translations/README.ja.md)をインストールする必要があります。
ターミナルでこのコマンドを実行することで、サーバーが正常に動作しているかどうかをテストすることができます。
```sh
curl http://localhost:5000/api
# -> 結果として "Bank API v1.0.0" を返す必要があります。
```
---
## 状態管理を再考する
[前回のレッスン](../../3-data/translations/README.ja.md)では、現在ログインしているユーザーの銀行データを含むグローバル変数 `account` を使って、アプリの基本的な状態の概念を紹介しました。しかし、現在の実装にはいくつかの欠陥があります。ダッシュボード上でページをリフレッシュしてみてください。何が起こるのでしょうか?
現在のコードには3つの問題があります。
- ブラウザをリフレッシュするとログインページに戻るため、状態は保持されません
- 状態を変更する関数が複数あります。アプリが大きくなると、変更を追跡するのが難しくなり、1つの更新を忘れがちになります
- 状態が片付かず、*Logout* をクリックしてもログインページになってもアカウントデータが残っています
これらの問題に一つずつ対処するためにコードを更新することもできますが、コードの重複が多くなり、アプリがより複雑になり、メンテナンスが難しくなります。あるいは、数分間小休止して、戦略を再考することもできます。
> ここで本当に解決しようとしている問題は何か?
[状態管理](https://en.wikipedia.org/wiki/State_management)は、この2つの特定の問題を解決するための良いアプローチを見つけることがすべてです。
- アプリ内のデータフローをわかりやすく保つには?
- アプリ内のデータフローを理解しやすい状態に保つには?
これらの問題を解決したら、他の問題はすでに解決されているか、簡単に解決できるようになっているかもしれません。これらの問題を解決するための多くの可能なアプローチがありますが、ここでは、**データとそれを変更する方法**を集中化することで構成される一般的な解決策を採用します。データの流れは次のようになります。
![HTML、ユーザーアクション、状態間のデータフローを示すスキーマ](../images/data-flow.png)
> ここでは、データが自動的にビューの更新のトリガーとなる部分は、[Reactive Programming](https://en.wikipedia.org/wiki/Reactive_programming)のより高度な概念に関連しているので、ここでは取り上げません。深く掘り下げたい方には良いフォローアップテーマになるでしょう。
✅ 状態管理へのさまざまなアプローチを持つライブラリはたくさんありますが、[Redux](https://redux.js.org) は人気のあるオプションです。大規模な Web アプリケーションで直面する可能性のある問題や、それをどのように解決できるかを学ぶための良い方法として、使用されている概念やパターンを見てみましょう。
### タスク
まずは少しリファクタリングをしてみましょう。`account` 宣言を置換します。
```js
let account = null;
```
このようにします。
```js
let state = {
account: null
};
```
このアイデアは、単一のステートオブジェクトにすべてのアプリデータを*中央集権化することです。今のところは `account` があるだけなので、あまり変化はありませんが、進化のためのパスを作成します。
また、これを使って関数を更新しなければなりません。関数 `register()``login()` において、`account = ...``state.account = ...` に置き換えてください。
関数 `updateDashboard()` の先頭に以下の行を追加します。
```js
const account = state.account;
```
今回のリファクタリングだけではあまり改善は見られませんでしたが、次の変更のための基礎を固めようと考えたのです。
## データ変更の追跡
データを保存するために `state` オブジェクトを配置したので、次のステップは更新を一元化することです。目的は、いつ変更があったのか、いつ変更が発生したのかを簡単に把握できるようにすることです。
`state` オブジェクトに変更が加えられないようにするためには、`state` オブジェクトを [*immutable*](https://en.wikipedia.org/wiki/Immutable_object) と考えるのも良い方法です。これはまた、何かを変更したい場合には新しいステートオブジェクトを作成しなければならないことを意味します。このようにすることで、潜在的に望ましくない[副作用](https://ja.wikipedia.org/wiki/%E5%89%AF%E4%BD%9C%E7%94%A8_(%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0))についての保護を構築し、アンドゥ/リドゥの実装のようなアプリの新機能の可能性を開くと同時に、デバッグを容易にします。例えば、ステートに加えられたすべての変更をログに記録し、バグの原因を理解するために変更の履歴を保持することができます。
JavaScript では、[`Object.freeze()`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) を使って、オブジェクトの不変バージョンを作成することができます。不変オブジェクトに変更を加えようとすると例外が発生します。
✅ *浅い*不変オブジェクトと*深い*不変オブジェクトの違いを知っていますか? それについては [こちら](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze#What_is_shallow_freeze) を参照してください。
### タスク
新しい `updateState()` 関数を作成してみましょう。
```js
function updateState(property, newData) {
state = Object.freeze({
...state,
[property]: newData
});
}
```
この関数では、新しいステートオブジェクトを作成し、[*spread (`...`) operator*](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax#spread_in_object_literals) を使用して前のステートからデータをコピーしています。次に、[ブラケット表記](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Working_with_Objects#objects_and_properties) `[property]` を使用して、ステートオブジェクトの特定のプロパティを新しいデータでオーバーライドします。最後に、`Object.freeze()`を使ってオブジェクトをロックし、変更を防ぎます。今のところ、`account` プロパティだけがステートに保存されていますが、この方法では必要なだけのプロパティをステートに追加することができます。
また、`state` の初期化を更新して初期状態も凍結されるようにします。
```js
let state = Object.freeze({
account: null
});
```
その後、`state.account = result;` の代入を `state.account = result;` に置き換えて `register` 関数を更新します。
```js
updateState('account', result);
```
同じことを `login` 関数で行い、`state.account = data;` に置き換えます。
```js
updateState('account', data);
```
ここで、ユーザーが *Logout* をクリックしたときにアカウントデータがクリアされない問題を修正します。
新しい関数 `logout()` を作成します。
```js
function logout() {
updateState('account', null);
navigate('/login');
}
```
`updateDashboard()` で、リダイレクト `return navigate('/login');``return logout()` に置き換えてください。
新しいアカウントを登録して、ログアウトとログインを繰り返してみて、すべてが正常に動作することを確認してください。
> ヒント: `updateState()` の下部に `console.log(state)` を追加し、ブラウザの開発ツールでコンソールを開くことで、すべての状態の変化を見ることができます。
## 状態を維持する
ほとんどの Web アプリは、データを保持しておかないと正常に動作しません。すべての重要なデータは通常、データベースに保存され、サーバー API を介してアクセスされます。しかし、時には、より良いユーザーエクスペリエンスや読み込みパフォーマンスを向上させるために、ブラウザ上で実行されているクライアントアプリのデータを永続化することも興味深いことです。
ブラウザにデータを永続化する場合、いくつかの重要な質問があります。
- *データは機密性の高いものでしょうか?* ユーザーパスワードなどの機密性の高いデータをクライアントに保存することは避けるべきです
- *このデータをどのくらいの期間保存する必要がありますか?* このデータにアクセスするのは現在のセッションのためだけですか、それとも永遠に保存したいですか?
Web アプリ内の情報を保存する方法は、目的に応じて複数あります。例えば、URL を使用して検索クエリを保存し、ユーザー間で共有できるようにすることができます。また、[認証](https://en.wikipedia.org/wiki/Authentication)情報のように、データをサーバーと共有する必要がある場合は、[HTTP クッキー](https://developer.mozilla.org/ja/docs/Web/HTTP/Cookies)を使用することもできます。
もう一つの選択肢は、データを保存するための多くのブラウザ API のうちの一つを使用することです。その中でも特に興味深いものが2つあります。
- [`localStorage`](https://developer.mozilla.org/ja/docs/Web/API/Window/localStorage): [Key/Value ストア](https://en.wikipedia.org/wiki/Key%E2%80%93value_database)は、異なるセッションにまたがって現在の Web サイトに固有のデータを永続化することができます。保存されたデータは期限切れになることはありません
- [`sessionStorage`](https://developer.mozilla.org/ja/docs/Web/API/Window/sessionStorage): これは `localStorage` と同じように動作しますが、保存されたデータはセッションの終了時(ブラウザが閉じられた時)に消去されます
これらの API はどちらも[文字列](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String)しか保存できないことに注意してください。複雑なオブジェクトを格納したい場合は、[`JSON.stringify()`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) を使って [JSON](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/JSON) 形式にシリアライズする必要があります。
✅ サーバーで動作しない Web アプリを作成したい場合は、[`IndexedDB` API](https://developer.mozilla.org/ja/docs/Web/API/IndexedDB_API) を使用してクライアント上にデータベースを作成することも可能です。これは高度なユースケースや、かなりの量のデータを保存する必要がある場合には、使用するのがより複雑になるため、予約されています。
### タスク
ユーザーが明示的に *Logout* ボタンをクリックするまでログインしたままにしたいので、`localStorage` を使ってアカウントデータを保存します。まず、データを保存するためのキーを定義しましょう。
```js
const storageKey = 'savedAccount';
```
そして、この行を `updateState()` 関数の最後に追加します。
```js
localStorage.setItem(storageKey, JSON.stringify(state.account));
```
これで、以前はすべての状態の更新を一元化していたので、ユーザーアカウントのデータは永続化され、常に最新の状態になります。ここからが、以前のすべてのリファクタリングの恩恵を受け始めるところです 🙂。
データが保存されているので、アプリが読み込まれたときに復元することにも気を配らなければなりません。初期化コードが増えてくるので、`app.js` の下部に以前のコードも含めた `init` 関数を新たに作成しておくといいかもしれません。
```js
function init() {
const savedAccount = localStorage.getItem(storageKey);
if (savedAccount) {
updateState('account', JSON.parse(savedAccount));
}
// 前回の初期化コード
window.onpopstate = () => updateRoute();
updateRoute();
}
init();
```
ここでは保存されたデータを取得し、もしあればそれに応じて状態を更新します。ページの更新中に状態に依存するコードがあるかもしれないので、ルートを更新する前にこれを行うことが重要です。
アカウントデータを保持しているので、*ダッシュボード* ページをアプリケーションのデフォルトページにすることもできます。データが見つからない場合は、ダッシュボードが *ログイン* ページにリダイレクトします。`updateRoute()` で、フォールバックの `return navigate('/login');``return navigate('dashboard');` に置き換えます。
アプリでログインしてページを更新してみてください。このアップデートで初期の問題はすべて解決しました。
## データの更新
...しかし、我々はまた、新しいものを作ったかもしれません。おっと!
`test` アカウントを使ってダッシュボードに行き、ターミナルで以下のコマンドを実行して新しいトランザクションを作成します。
```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
```
ダッシュボードのページをブラウザで更新してみてください。どうなりますか?新しいトランザクションが表示されましたか?
この状態は `localStorage` のおかげで無期限に保持されますが、アプリからログアウトして再度ログインするまで更新されません。
これを修正するために考えられる戦略の1つは、ダッシュボードがロードされるたびにアカウントデータをリロードして、データのストールを回避することです。
### タスク
新しい関数 `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);
}
```
このメソッドは現在ログインしているかどうかをチェックし、サーバからアカウントデータをリロードします。
`refresh` という名前の別の関数を作成します。
```js
async function refresh() {
await updateAccountData();
updateDashboard();
}
```
これはアカウントデータを更新し、ダッシュボードページの HTML を更新する処理を行います。ダッシュボードルートがロードされたときに呼び出す必要があるものです。これでルート定義を更新します。
```js
const routes = {
'/login': { templateId: 'login' },
'/dashboard': { templateId: 'dashboard', init: refresh }
};
```
ダッシュボードをリロードしてみると、更新されたアカウントデータが表示されるはずです。
---
## 🚀 チャレンジ
ダッシュボードがロードされるたびにアカウントデータをリロードするようになりましたが、すべてのアカウントデータを保持する必要があると思いますか?
アプリが動作するために絶対に必要なものだけを含むように、`localStorage` から保存およびロードされるものを変更するために、一緒に作業してみてください。
## レッスン後の小テスト
[レッスン後の小テスト](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/48?loc=ja)
## 課題
[「トランザクションの追加」ダイアログの実装](assignment.ja.md)
課題を終えた後の結果の一例です。
![「トランザクションの追加」ダイアログの例を示すスクリーンショット](../images/dialog.png)

View File

@@ -1,281 +0,0 @@
# 은행 앱 제작하기 파트 4: 상태 관리의 컨셉
## 강의 전 퀴즈
[Pre-lecture quiz](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/47?loc=ko)
### 소개
웹 애플리케이션이 커지면서, 모든 데이터 흐름을 추적하는 것은 어렵습니다. 어떤 코드가 데이터를 가져오고, 어떤 페이지가 데이터를 사용하고, 언제 어디서 갱신해야 하는지... 관리하기 어려운 복잡한 코드로 끝날 수 있습니다. 이는 앱의 여러 페이지가 서로 데이터를 공유하는 경우에 특히 더 그렇습니다, 예시로 사용자 데이터. *상태 관리*의 컨셉은 항상 모든 종류의 프로그램에 존재했지만, 웹 앱이 계속 복잡해지면서 이제는 개발하면서 고려해야 할 키 포인트가 되었습니다.
이 최종 부분에서는, 상태 관리하는 방법을 다시 생각해보며, 언제든 브라우저 새로고침을 지원하고, 사용자 세션에서 데이터를 유지하기 위해서 작성한 앱을 살펴 보겠습니다.
### 준비물
이 강의의 웹 앱 [data fetching](../../3-data/translations/README.ko.md) 파트를 완료해둬야 합니다. [Node.js](https://nodejs.org)와 [run the server API](../../api/README.md)를 로컬에 설치해야 계정 정보를 관리할 수 있습니다.
터미널에서 다음 명령을 수행하여 서버가 잘 실행되고 있는지 테스트할 수 있습니다:
```sh
curl http://localhost:5000/api
# -> should return "Bank API v1.0.0" as a result
```
---
## 상태 관리에 대하여 다시 생각하기
[이전 강의](../../3-data/translations/README.ko.md)에서는, 현재 로그인한 사용자의 은행 데이터를 포함하는 전역 `account` 변수를 사용하여 앱에 기초 상태 개념을 도입했습니다. 그러나, 현재 구현에는 조금 취약점이 있습니다. 대시보드에서 페이지를 새로 고쳐보기 바랍니다. 무슨 일이 일어나고 있나요?
현재 코드에는 3가지 이슈가 있습니다:
- 브라우저를 새로 고치면 로그인 페이지로 돌아가기 때문에, 상태가 유지되지 않습니다.
- 상태를 바꾸는 여러 함수들이 있습니다. 앱이 커지면서, 변경점을 추적하기 어렵고 갱신한 것을 잊어버리기 쉽습니다.
- 상태가 정리되지 않았습니다, *로그아웃*을 클릭하면 로그인 페이지에 있어도 계정 데이터가 그대로 유지됩니다.
이런 이슈를 하나씩 해결하기 위해 코드를 갱신할 수는 있지만, 코드 중복이 더 많이 발생되고 앱이 더 복잡해져서 유지 관리가 어려워집니다. 또는 몇 분 동안 잠시 멈춰서 다시 기획할 수도 있습니다.
> 여기서 우리가 실제로 해결할 문제는 무엇인가요?
[State management](https://en.wikipedia.org/wiki/State_management)는 다음 2가지 특정한 문제를 해결하기 위해 좋은 접근 방식을 찾습니다:
- 이해하기 쉽게 앱의 데이터 흐름을 유지하는 방법은 무엇인가요?
- 상태 데이터를 사용자 인터페이스와 항상 동기화하는 방법은 있나요 (혹은 그 반대로)?
이런 문제를 해결한 후에는 다른 이슈가 이미 고쳐졌거나 더 쉽게 고칠 수 있습니다. 이러한 문제를 해결하기 위한 여러 가능한 방식들이 있지만, **데이터를 중앙 집중화하고 변경하는 방법**으로 구성된 공통 솔루션을 사용합니다. 데이터 흐름은 다음과 같습니다:
![Schema showing the data flows between the HTML, user actions and state](.././images/data-flow.png)
> 데이터와 뷰 갱신을 자동으로 연결하는 부분은, [Reactive Programming](https://en.wikipedia.org/wiki/Reactive_programming)의 고급 컨셉과 연결되었으므로 여기서 다루지는 않습니다. 깊게 분석한다면 좋게 팔로우 업할 주제입니다.
✅ 상태 관리에 대한 접근 방식이 다른 수 많은 라이브러리가 있으며, [Redux](https://redux.js.org)는 인기있는 옵션입니다. 큰 웹 앱에서 마주할 수 있는 잠재적 이슈와 해결 방식을 알 수 있으므로 사용된 컨셉과 패턴을 살펴보세요.
### 작업
조금 리팩토링을 해보면서 시작해봅니다. `account` 선언을 바꿉니다:
```js
let account = null;
```
With:
```js
let state = {
account: null
};
```
이 아이디어는 모든 앱 데이터를 단일 상태 개체에서 *중앙에 모으는* 것 입니다. 현재 상태에서는 `account`만 가지고 있으므로 많이 변하지 않지만, 발전을 위한 길을 닦아둡니다.
또한 그것을 사용하여 함수를 갱신해야 합니다. `register()``login()` 함수에서, `account = ...``state.account = ...`로 바꿉니다.
`updateDashboard()` 함수 상단에, 이 줄을 추가합니다:
```js
const account = state.account;
```
이 리팩토링만으로는 많은 개선이 이루어지지 않지만, 아이디어는 다음 변경점의 토대를 마련해줍니다.
## 데이터 변경 추적하기
데이터로 저장할 `state` 객체를 두었으므로, 다음 단계는 갱신 작업을 중앙 집중화하는 것입니다. 목표는 모든 변경점과 발생 시점을 쉽게 ​​추적하는 것입니다.
`state` 객체가 변경되지 않으려면, [*immutable*](https://en.wikipedia.org/wiki/Immutable_object)한 것으로 간주하는 것이 좋습니다. 즉, 전혀 수정할 수 없다는 점을 의미합니다. 또한 변경하려는 경우에는 새로운 상태 객체를 만들어야 된다는 점을 의미합니다. 이렇게 하면, 잠재적으로 원하지 않는 [side effects](https://en.wikipedia.org/wiki/Side_effect_(computer_science))에 보호하도록 만들고, undo/redo를 구현하는 것 처럼 앱의 새로운 기능에 대한 가능성을 열어 디버깅을 더 쉽게 만듭니다. 예를 들자면, 상태에 대한 모든 변경점을 남기고 유지하여 버그의 원인을 파악할 수 있습니다.
JavaScript에서, [`Object.freeze()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze)를 사용하여 변경할 수 없는 버전의 객체를 만들 수 있습니다. 변경 불가능한 객체를 바꾸려고 하면 예외가 발생합니다.
✅ *shallow*와 *deep* 불변 객체의 차이점을 알고 계시나요? [here](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze#What_is_shallow_freeze)에서 읽을 수 있습니다.
### 작업
새로운 `updateState()` 함수를 만듭니다:
```js
function updateState(property, newData) {
state = Object.freeze({
...state,
[property]: newData
});
}
```
이 함수에서는, 새로운 상태 객체를 만들고 [*spread (`...`) operator*](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Spread_in_object_literals)로 이전 상태의 데이터를 복사합니다. 그러고 할당을 위해 [bracket notation](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Working_with_Objects#Objects_and_properties) `[property]`를 사용하여 상태 객체의 특정한 속성을 새로운 데이터로 다시 정의합니다. 최종적으로, 변경되는 것을 막기 위해 `Object.freeze()`를 사용하여 객체를 잠급니다. 지금 상태에는 `account` 속성만 저장되어 있지만, 이 접근 방식으로 상태에 필요한 순간마다 많은 속성들을 추가할 수 있습니다.
또한 초기 상태가 동결되도록 `state` 초기화 작업도 갱신합니다:
```js
let state = Object.freeze({
account: null
});
```
그런 다음, `state.account = result;` 할당을 이 것으로 대체하여 `register` 함수를 갱신합니다:
```js
updateState('account', result);
```
`login` 함수에서도 동일하게 진행하고, `state.account = data;`도 이 것으로 바꿉니다:
```js
updateState('account', data);
```
이제 사용자가 *Logout*을 클릭 할 때 계정 데이터가 지워지지 않는 이슈를 해결할 수 있습니다.
새로운 함수 `logout()`을 만듭니다:
```js
function logout() {
updateState('account', null);
navigate('/login');
}
```
`updateDashboard()` 에서, 리다이렉션하는 `return navigate('/login');``return logout()`으로 바꿉니다;
새로운 계정으로 가입을 시도하면, 로그아웃하고 다시 로그인하여 모두 잘 작동하는지 확인합니다.
> Tip: `updateState()` 하단에 `console.log(state)`를 추가하고 브라우저의 개발 도구에서 콘솔을 열면 모든 상태 변경점을 볼 수 있습니다.
## 상태 유지하기
대부분 웹 앱이 잘 작동하려면 데이터를 유지할 필요가 있습니다. 모든 중요한 데이터는 일반적으로 데이터베이스에 저장되고 우리 케이스에는 사용자 계정 데이터처럼, 서버 API를 통해 접근됩니다. 그러나 때로는, 더 좋은 사용자 경험이나 로딩 퍼포먼스를 개선하기 위해서, 브라우저에서 실행중인 클라이언트 앱에 일부 데이터를 유지하는 것도 흥미롭습니다.
브라우저에서 데이터를 유지하려면, 스스로에게 몇 중요한 질문을 해야합니다:
- *민감한 데이터인가요?* 사용자 암호와 같은, 민감한 데이터는 클라이언트에 저장하지 않아야 합니다.
- *데이터를 얼마나 오래 보관해야 하나요?* 현재 세션에서만 데이터에 접근하거나 계속 저장할 계획인가요?
달성하려는 목표에 따라, 웹 앱 안에서 정보를 저장하는 방법에는 여러 가지가 있습니다. 예를 들면, URL을 사용하여 검색 쿼리를 저장하고, 사용자끼리 공유할 수 있습니다. [authentication](https://en.wikipedia.org/wiki/Authentication) 정보처럼, 데이터를 서버와 공유해야하는 경우에도 [HTTP cookies](https://developer.mozilla.org/docs/Web/HTTP/Cookies)를 사용할 수 있습니다.
다른 옵션으로는 데이터 저장을 위해 여러 브라우저 API 중 하나를 사용하는 것입니다. 그 중 2가지가 특히 흥미롭습니다:
- [`localStorage`](https://developer.mozilla.org/docs/Web/API/Window/localStorage): [Key/Value store](https://en.wikipedia.org/wiki/Key%E2%80%93value_database)는 다른 세션에서 현재 웹 사이트에 대한 특정 데이터를 유지할 수 있습니다. 저장된 데이터는 만료되지 않습니다.
- [`sessionStorage`](https://developer.mozilla.org/docs/Web/API/Window/sessionStorage): 이는 세션이 끝날 때(브라우저가 닫힐 때)에 저장된 데이터가 지워진다는 점을 제외하면 `localStorage`와 동일하게 작동합니다.
이 두 API는 모두 [strings](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)만 저장할 수 있습니다. 복잡한 객체를 저장하려면, [`JSON.stringify()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify)를 사용하여 [JSON](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON) 포맷으로 직렬화해야 합니다.
✅ 서버에서 동작하지 않는 웹 앱을 만드려면, [`IndexedDB` API](https://developer.mozilla.org/docs/Web/API/IndexedDB_API)로 클라이언트에 데이터베이스를 만들 수도 있습니다. 이는 고급 사용 케이스이거나, 사용하기 복잡한 많은 양의 데이터를 저장해야 할 때에 사용하도록 되어있습니다.
### 작업
*Logout* 버튼을 명시적으로 클릭할 때까지 로그인 상태가 유지되기를 원하므로, `localStorage`로 계정 데이터를 저장합니다. 먼저, 데이터를 저장하는 데 사용할 키를 정의하겠습니다.
```js
const storageKey = 'savedAccount';
```
그러고 `updateState()` 함수의 하단에 이 줄을 추가합니다:
```js
localStorage.setItem(storageKey, JSON.stringify(state.account));
```
이를 통해, 이전의 모든 상태를 갱신하는 작업이 가운데로 모임에 따라 사용자 계정 데이터가 유지되고 항상 최신-상태를 유지합니다. 이것으로 이전 모든 리팩터링 작업의 혜택을 받기 시작했습니다 🙂.
더 많은 초기화 코드를 가지게 될 예정이므로 새로운 `init` 함수를 만드는 것이 좋습니다, 여기에는 `app.js`의 하단에 이전 코드가 포함됩니다:
```js
function init() {
const savedAccount = localStorage.getItem(storageKey);
if (savedAccount) {
updateState('account', JSON.parse(savedAccount));
}
// Our previous initialization code
window.onpopstate = () => updateRoute();
updateRoute();
}
init();
```
여기에서 저장된 데이터를 검색하고, 그에 따라서 상태를 갱신합니다. 페이지를 갱신하다가 상태에 의존하는 코드가 있을 수 있으므로, 라우터를 갱신하기 *전에* 하는 것이 중요합니다.
이제 계정 데이터를 유지하고 있으므로, *대시보드* 페이지를 애플리케이션 기본 페이지로 만들 수도 있습니다. 데이터가 없다면, 대시보드는 언제나 *로그인* 페이지로 리다이렉팅합니다. `updateRoute ()`에서, `return navigate('/login');``return navigate('dashboard');`로 바꿉니다.
이제 앱에 로그인하고 페이지를 새로 고쳐보면, 대시보드에 남아있어야 합니다. 이 업데이트로 모든 초기 이슈를 처리했습니다...
## 데이터 새로 고치기
...그러나 새로운 것을 만들 수도 있습니다. 웁스!
`test` 계정을 사용하여 대시보드로 이동하면, 터미널에서 이 명령을 실행하여 새로운 트랜잭션을 만듭니다:
```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
```
지금 브라우저에서 대시보드 페이지를 새로 고쳐봅니다. 어떤 일이 일어났나요? 새로운 트랜잭션이 보이나요?
상태는 `localStorage` 덕분에 무한으로 유지하지만, 앱에서 로그아웃하고 다시 로그인할 때까지 갱신하지 않는다는 점을 의미합니다!
해결할 수 있는 한 가지 전략은 대시보드를 불러올 때마다 계정 데이터를 다시 불러와서, 데이터가 오래되는 현상을 방지하는 것 입니다.
### 작업
새로운 함수 `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);
}
```
이 메소드는 현재 로그인되어 있는지 본 다음에 서버에서 계정 데이터를 다시 불러옵니다.
`refresh`라고 이름을 지은 또 다른 함수를 만듭니다:
```js
async function refresh() {
await updateAccountData();
updateDashboard();
}
```
이는 계정 데이터를 갱신하고나서, 대시보드 페이지의 HTML도 갱신하게 됩니다. 대시보드 라우터를 불러올 때마다 호출해야 합니다. 다음으로 라우터 정의를 갱신합니다:
```js
const routes = {
'/login': { templateId: 'login' },
'/dashboard': { templateId: 'dashboard', init: refresh }
};
```
지금 대시보드를 다시 불러옵니다, 갱신된 계정 데이터를 볼 수 있어야 합니다.
---
## 🚀 도전
이제 대시보드를 불러올 때마다 계정 데이터가 다시 불러와지는데, 여전히 *모든 계정* 데이터를 유지해야 된다고 생각하나요?
앱이 동작하는 데 꼭 필요한 것만 있도록 `localStorage` 에 저장하고 불러온 항목을 함께 바꿔봅니다.
## 강의 후 퀴즈
[Post-lecture quiz](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/48?loc=ko)
## 과제
[Implement "Add transaction" dialog](../assignment.md)
다음은 과제를 완료한 뒤의 예시 결과입니다:
![Screenshot showing an example "Add transaction" dialog](.././images/dialog.png)

View File

@@ -1,281 +0,0 @@
# Bina Aplikasi Perbankan Bahagian 4: Konsep State Management
## Kuiz Pra Kuliah
[Kuiz Pra Kuliah](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/47)
### Pengenalan
Apabila aplikasi web berkembang, menjadi cabaran untuk mengawasi semua aliran data. Kod mana yang mendapat data, halaman mana yang menggunakannya, di mana dan kapan ia perlu dikemas kini ... mudah untuk berakhir dengan kod tidak kemas yang sukar dijaga. Ini benar terutamanya apabila anda perlu berkongsi data di antara halaman aplikasi anda yang berbeza, misalnya data pengguna. Konsep *state management* selalu ada dalam semua jenis program, tetapi ketika aplikasi web terus berkembang dalam kerumitan, kini menjadi titik penting untuk dipikirkan semasa pembangunan.
Pada bahagian akhir ini, kita akan melihat aplikasi yang kita buat untuk memikirkan kembali bagaimana keadaan dikendalikan, yang membolehkan sokongan penyegaran penyemak imbas pada bila-bila masa, dan data yang berterusan sepanjang sesi pengguna.
### Prasyarat
Anda perlu menyelesaikan bahagian [pengambilan data](../../3-data/translations/README.ms.md) pada aplikasi web untuk pelajaran ini. Anda juga perlu memasang [Node.js](https://nodejs.org) dan [jalankan API pelayan](../../api/README.ms.md) secara tempatan supaya anda dapat menguruskan data akaun.
Anda boleh menguji bahawa pelayan berjalan dengan betul dengan menjalankan perintah ini di terminal:
```sh
curl http://localhost:5000/api
# -> should return "Bank API v1.0.0" as a result
```
---
## Fikirkan semula state management
Dalam [pelajaran sebelumnya](../../3-data/translations/README.md), kami memperkenalkan konsep dasar keadaan dalam aplikasi kami dengan pemboleh ubah `akaun` global yang mengandungi data bank untuk pengguna yang sedang log masuk. Walau bagaimanapun, pelaksanaan kami sekarang mempunyai beberapa kekurangan. Cuba muat semula halaman semasa anda berada di papan pemuka. Apa yang berlaku?
Terdapat 3 masalah dengan kod semasa:
- Keadaan tidak berterusan, kerana penyegaran penyemak imbas membawa anda kembali ke halaman log masuk.
- Terdapat pelbagai fungsi yang mengubah keadaan. Apabila aplikasinya berkembang, ini akan menyukarkan untuk mengesan perubahan dan mudah untuk melupakan pembaharuan.
- Keadaan tidak dibersihkan, apabila anda mengklik *Logout* data akaun masih ada walaupun anda berada di halaman log masuk.
Kami dapat mengemas kini kod kami untuk mengatasi masalah ini satu demi satu, tetapi ini akan membuat lebih banyak pendua kod dan menjadikan aplikasi lebih rumit dan sukar untuk dijaga. Atau kita boleh berhenti sebentar dan memikirkan semula strategi kita.
> Masalah apa yang sebenarnya ingin kita selesaikan di sini?
[State Management](https://en.wikipedia.org/wiki/State_management) adalah mengenai mencari pendekatan yang baik untuk menyelesaikan dua masalah tertentu:
- Bagaimana agar aliran data dalam aplikasi dapat difahami?
- Bagaimana cara menjaga data keadaan selalu selari dengan antara muka pengguna (dan sebaliknya)?
Setelah menyelesaikan masalah ini, masalah lain yang mungkin anda miliki mungkin sudah selesai atau menjadi lebih mudah untuk diselesaikan. Terdapat banyak kemungkinan pendekatan untuk menyelesaikan masalah ini, tetapi kami akan menggunakan penyelesaian bersama yang terdiri daripada **memusatkan data dan cara mengubahnya**. Aliran data akan seperti ini:
![Skema yang menunjukkan aliran data antara HTML, tindakan pengguna dan keadaan](../images/data-flow.png)
> Kami tidak akan membahas di sini bahagian di mana data secara automatik mencetuskan kemas kini paparan, kerana ia berkaitan dengan konsep [Pemrograman Reaktif](https://en.wikipedia.org/wiki/Reactive_programming). Ini adalah subjek susulan yang baik jika anda ingin menyelam.
✅ Terdapat banyak perpustakaan di luar sana dengan pendekatan yang berbeza untuk pengurusan negeri, [Redux](https://redux.js.org) menjadi pilihan yang popular. Lihat konsep dan corak yang digunakan kerana ini sering kali merupakan kaedah yang baik untuk mengetahui potensi masalah yang mungkin anda hadapi dalam aplikasi web besar dan bagaimana ia dapat diselesaikan.
### Tugas
Kita akan mulakan dengan sedikit refactoring. Ganti pernyataan `akaun`:
```js
let account = null;
```
With:
```js
let state = {
account: null
};
```
Ideanya adalah untuk *memusatkan* semua data aplikasi kami dalam satu objek keadaan. Kami hanya mempunyai `akaun` untuk saat ini di negeri ini sehingga tidak banyak berubah, tetapi ini menciptakan jalan untuk evolusi.
Kita juga harus mengemas kini fungsi menggunakannya. Dalam fungsi `register()` dan `login()`, ganti `account = ...` dengan `state.account = ...`;
Di bahagian atas fungsi `updateDashboard()`, tambahkan baris ini:
```js
const account = state.account;
```
Pemfaktoran semula ini dengan sendirinya tidak membawa banyak peningkatan, tetapi ideanya adalah untuk meletakkan asas untuk perubahan selanjutnya.
## Jejak perubahan data
Sekarang kita telah meletakkan objek `state` untuk menyimpan data kita, langkah seterusnya adalah memusatkan kemas kini. Tujuannya adalah untuk menjadikannya lebih mudah untuk mengikuti setiap perubahan dan kapan ia berlaku.
Untuk mengelakkan berlakunya perubahan pada objek `state`, adalah praktik yang baik untuk mempertimbangkannya [*tidak berubah*](https://en.wikipedia.org/wiki/Immutable_object), yang bermaksud bahawa ia tidak dapat diubah sama sekali. Ini juga bermaksud bahawa anda harus membuat objek keadaan baru jika anda ingin mengubah apa-apa di dalamnya. Dengan melakukan ini, anda membina perlindungan mengenai [kesan sampingan](https://en.wikipedia.org/wiki/Side_effect_ (computer_science)), dan membuka kemungkinan untuk ciri baru dalam aplikasi anda seperti melaksanakan undo / redo, sambil mempermudah debug. Sebagai contoh, anda boleh mencatat setiap perubahan yang dibuat ke negeri dan menyimpan sejarah perubahan untuk memahami sumber pepijat.
Dalam JavaScript, anda boleh menggunakan [`Object.freeze()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) untuk membuat versi yang tidak berubah dari sebuah objek. Sekiranya anda cuba membuat perubahan pada objek yang tidak dapat diubah, pengecualian akan ditimbulkan.
✅ Adakah anda tahu perbezaan antara objek *cetek* dan *dalam* tidak berubah? Anda boleh membacanya [di sini](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze#What_is_shallow_freeze).
### Tugas
Mari buat fungsi `updateState()` baru:
```js
function updateState(property, newData) {
state = Object.freeze({
...state,
[property]: newData
});
}
```
Dalam fungsi ini, kami membuat objek keadaan baru dan menyalin data dari keadaan sebelumnya menggunakan operator [*spread (`...`) operator*](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Spread_in_object_literals). Kemudian kami menimpa harta benda objek tertentu dengan data baru menggunakan [notasi kurungan](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Working_with_Objects#Objects_and_properties) `[property]` untuk tugasan. Akhirnya, kita mengunci objek untuk mengelakkan pengubahsuaian menggunakan `Object.freeze()`. Kami hanya menyimpan harta `akaun` di negeri ini buat masa ini, tetapi dengan pendekatan ini anda dapat menambahkan seberapa banyak harta tanah yang anda perlukan di negeri ini.
Kami juga akan mengemas kini inisialisasi `state` untuk memastikan keadaan awal juga dibekukan:
```js
let state = Object.freeze({
account: null
});
```
After that, update the `register` function by replacing the `state.account = result;` assignment with:
```js
updateState('account', result);
```
Do the same with the `login` function, replacing `state.account = data;` with:
```js
updateState('account', data);
```
Kami sekarang akan mengambil kesempatan untuk memperbaiki masalah data akaun yang tidak dihapus ketika pengguna mengklik *Logout*.
Buat fungsi baru `logout()`:
```js
function logout() {
updateState('account', null);
navigate('/login');
}
```
Di `updateDashboard()`, ganti pengalihan `return navigate('/login');` dengan `return logout()`;
Cuba daftarkan akaun baru, log keluar dan masuk sekali lagi untuk memeriksa bahawa semuanya masih berfungsi dengan betul.
> Petua: anda dapat melihat semua perubahan keadaan dengan menambahkan `console.log(state)` di bahagian bawah `updateState()` dan membuka konsol di alat pengembangan penyemak imbas anda.
## Kekalkan keadaan
Sebilangan besar aplikasi web perlu mengekalkan data agar dapat berfungsi dengan betul. Semua data kritikal biasanya disimpan di pangkalan data dan diakses melalui API pelayan, seperti data akaun pengguna dalam kes kami. Tetapi kadang-kadang, juga menarik untuk mengekalkan beberapa data pada aplikasi klien yang berjalan di penyemak imbas anda, untuk pengalaman pengguna yang lebih baik atau untuk meningkatkan prestasi pemuatan.
Apabila anda ingin menyimpan data dalam penyemak imbas anda, terdapat beberapa soalan penting yang harus anda tanyakan kepada diri sendiri:
- *Adakah data sensitif?* Anda harus mengelakkan menyimpan data sensitif pada pelanggan, seperti kata laluan pengguna.
- *Berapa lama anda perlu menyimpan data ini?* Adakah anda merancang untuk mengakses data ini hanya untuk sesi semasa atau adakah anda ingin menyimpannya selamanya?
Terdapat banyak cara untuk menyimpan maklumat di dalam aplikasi web, bergantung pada apa yang ingin anda capai. Sebagai contoh, anda boleh menggunakan URL untuk menyimpan pertanyaan carian, dan menjadikannya boleh dibagikan antara pengguna. Anda juga boleh menggunakan [kuki HTTP](https://developer.mozilla.org/docs/Web/HTTP/Cookies) jika data perlu dikongsi dengan pelayan, seperti [pengesahan](https://en.wikipedia.org/wiki/Authentication) maklumat.
Pilihan lain adalah menggunakan salah satu dari banyak API penyemak imbas untuk menyimpan data. Dua daripadanya sangat menarik:
- [`localStorage`](https://developer.mozilla.org/docs/Web/API/Window/localStorage): a [Key/Value store](https://en.wikipedia.org/wiki/Key%E2%80%93value_database) yang memungkinkan untuk mengekalkan data khusus untuk laman web semasa di pelbagai sesi. Data yang disimpan di dalamnya tidak akan luput.
- [`sessionStorage`](https://developer.mozilla.org/docs/Web/API/Window/sessionStorage): ini berfungsi sama seperti `localStorage` kecuali data yang disimpan di dalamnya dibersihkan semasa sesi berakhir (semasa penyemak imbas ditutup).
Perhatikan bahawa kedua-dua API ini hanya membenarkan menyimpan [string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String). Sekiranya anda ingin menyimpan objek yang kompleks, anda perlu membuat siri ke siri [JSON](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON) menggunakan [`JSON.stringify()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify).
✅ Sekiranya anda ingin membuat aplikasi web yang tidak berfungsi dengan pelayan, anda juga boleh membuat pangkalan data pada klien menggunakan [`IndexedDB` API](https://developer.mozilla.org/docs/Web/API/IndexedDB_API). Yang ini dikhaskan untuk kes penggunaan lanjutan atau jika anda perlu menyimpan sejumlah besar data, kerana lebih kompleks untuk digunakan.
### Tugas
Kami mahu pengguna kami terus masuk sehingga mereka mengklik butang *Logout* secara eksplisit, jadi kami akan menggunakan `localStorage` untuk menyimpan data akaun. Pertama, mari tentukan kunci yang akan kami gunakan untuk menyimpan data kami.
```js
const storageKey = 'savedAccount';
```
Kemudian tambahkan baris ini pada akhir fungsi `updateState()`:
```js
localStorage.setItem(storageKey, JSON.stringify(state.account));
```
Dengan ini, data akaun pengguna akan dikekalkan dan sentiasa terkini semasa kami memusatkan semua kemas kini negeri sebelumnya. Di sinilah kita mula mendapat manfaat daripada semua reaktor kita sebelumnya 🙂.
Semasa data disimpan, kita juga harus menjaga memulihkannya ketika aplikasi dimuat. Oleh kerana kita akan mempunyai lebih banyak kod inisialisasi, mungkin ada baiknya membuat fungsi `init` baru, yang juga merangkumi kod sebelumnya di bahagian bawah `app.js`:
```js
function init() {
const savedAccount = localStorage.getItem(storageKey);
if (savedAccount) {
updateState('account', JSON.parse(savedAccount));
}
// Kod permulaan kami sebelumnya
window.onpopstate = () => updateRoute();
updateRoute();
}
init();
```
Di sini kami mengambil data yang disimpan, dan jika ada, kami akan mengemas kini keadaan dengan sewajarnya. Penting untuk melakukan ini *sebelum* mengemas kini laluan, kerana mungkin ada kod yang bergantung pada keadaan semasa kemas kini halaman.
Kami juga dapat menjadikan halaman *Dashboard* sebagai halaman lalai aplikasi kami, karena kami sekarang masih menyimpan data akun. Sekiranya tidak ada data, dashboard akan mengalihkan ke halaman *Login*. Dalam `updateRoute()`, ganti fallback `return navigate ('/login');` dengan `return navigate('dashboard');`.
Sekarang log masuk dalam aplikasi dan cuba memuat semula halaman, anda harus terus berada di papan pemuka. Dengan kemas kini itu, kami telah menangani semua masalah awal kami ...
## Muat semula data
... Tetapi kita mungkin juga telah membuat yang baru. Alamak!
Pergi ke papan pemuka menggunakan akaun `test`, kemudian jalankan perintah ini di terminal untuk membuat transaksi baru:
```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
```
Cuba muat semula halaman papan pemuka anda di penyemak imbas sekarang. Apa yang berlaku? Adakah anda melihat transaksi baru?
Keadaan ini berterusan selama-lamanya berkat `localStorage`, tetapi itu juga bermaksud ia tidak akan dikemas kini sehingga anda log keluar dari aplikasi dan log masuk semula!
Salah satu strategi yang mungkin untuk diperbaiki adalah memuat semula data akaun setiap kali dashboard dimuat, untuk mengelakkan data terhenti.
### Tugas
Buat fungsi baru `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);
}
```
Kaedah ini memeriksa bahawa kita sedang log masuk dan memuat semula data akaun dari pelayan.
Buat fungsi lain bernama `refresh`:
```js
async function refresh() {
await updateAccountData();
updateDashboard();
}
```
Yang ini mengemas kini data akaun, kemudian mengurus mengemas kini HTML halaman papan pemuka. Inilah yang perlu kita panggil semasa laluan papan pemuka dimuat. Kemas kini definisi laluan dengan:
```js
const routes = {
'/login': { templateId: 'login' },
'/dashboard': { templateId: 'dashboard', init: refresh }
};
```
Cuba muatkan semula papan pemuka sekarang, ia akan memaparkan data akaun yang dikemas kini.
---
## 🚀 Cabaran
Setelah kita memuatkan semula data akaun setiap kali papan pemuka dimuat, adakah anda fikir kita masih perlu meneruskan *semua data* akaun?
Cuba bekerjasama untuk mengubah apa yang disimpan dan dimuat dari `localStorage` untuk hanya memasukkan perkara yang benar-benar diperlukan agar aplikasi berfungsi.
## Post-Lecture Quiz
[Post-Lecture Quiz](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/48)
## Tugasan
[Laksanakan dialog "Tambah transaksi"](assignment.ms.md)
Inilah hasil contoh setelah menyelesaikan tugasan:
![Tangkapan skrin yang menunjukkan contoh dialog "Tambah transaksi"](../images/dialog.png)

View File

@@ -1,284 +0,0 @@
# 建立銀行網頁應用程式 Part 4 狀態控管的概念
## 課前測驗
[課前測驗](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/47?loc=zh_tw)
### 大綱
隨著網頁應用越來越龐大,追蹤資料流的動向也是一種挑戰。程式取得了何種資料、網頁如何處理它、何時何處被更新上去……這些很容易地導致程式碼凌亂而難以維護。尤其是當你需要在不同頁面上做資料共享時,好比說使用者的資料。*狀態控管(state management)* 的觀念已經存在於所有程式中,我們也開始需要在開發複雜的網頁應用程式時,注意這個關鍵點。
在這個最終章內,我們會總覽整個程式並重新思考該如何管理程式狀態,讓瀏覽器能在任何時刻做重新整理,在不同的使用者階段維持資料的狀態。
### 開始之前
你需要先完成[取得資料](../../3-data/translations/README.zh-tw.md)的網頁開發章節。你還需要安裝 [Node.js](https://nodejs.org) 並於本地端[執行伺服器 API](../../api/translations/README.zh-tw.md)以管理使用者資料。
你可以測試伺服器是否運作正常,在終端機中輸入指令:
```sh
curl http://localhost:5000/api
# -> should return "Bank API v1.0.0" as a result
```
---
## 思考狀態控管
在[前一堂課](../../3-data/translations/README.zh-tw.md)中,我們介紹了應用程式基本的狀態,全域變數 `account` 提供登入帳戶的相關銀行資料。然而,現在的專案存在著一些瑕疵。試著在儀表板介面中重新整理。發生了什麼事?
目前我們的程式碼有三個問題:
- 網頁狀態並沒有被儲存,當瀏覽器重新整理時,會被導回登入頁面。
- 有許多函式會修改網頁狀態。隨著應用程式變大,我們很難去追蹤之後的改變,時刻地去更新相關的網頁狀態。
- 網頁狀態並不完整,當你*登出*帳戶時,帳戶資訊仍然顯示在登入頁面上。
我們是可以逐一的解決這些問題,但這樣會創造出許多獨立的程式碼,讓應用程式更複雜而難以去管理。或者是我們停下來思考一下我們的策略。
> 我們究竟要解決什麼問題?
[狀態控管(State management)](https://en.wikipedia.org/wiki/State_management)可以為兩項問題提供良好的解決方案:
- 如何讓應用程式中的資料流容易理解?
- 如何讓網頁狀態一直與使用者介面,或是相關物件進行同步?
一旦你處理好這些問題,其他問題可以被簡化,甚至被一併解決。有許多可能的方法能解決這些問題,但我們使用一種常見的解法:**中心化資料與更新方式**。資料流會呈現下列模式:
![HTML、使用者行為與網頁狀態的架構圖](../images/data-flow.png)
> 我們不會處理如何讓資料同步觸發頁面的更新,這比較像是關於[回應式程式設計](https://zh.wikipedia.org/wiki/%E5%93%8D%E5%BA%94%E5%BC%8F%E7%BC%96%E7%A8%8B)的更進階知識。當你更深入網頁開發領域時,這是個很好的發展方向。
✅ 有許多函式庫提供狀態管理的方式,[Redux](https://redux.js.org) 就是常見的選擇。閱讀它的概念與運作模式,這是種有效的的學習方式,讓你在大型的網頁開發中預測潛在的風險,並預想解決方案。
### 課題
我們會先做一些程式重構。替換掉 `account` 的定義:
```js
let account = null;
```
變成:
```js
let state = {
account: null
};
```
這個構想是要*中心化*應用程式資料到一個狀態物件中。目前我們只有 `account` 在狀態中,但這能提供未來新增新功能的基礎。
我們還需要更新與它相關的函式。在函式 `register()``login()` ,將 `account = ...` 替換為 `state.account = ...`
在函式 `updateDashboard()` 的上方,加入此行:
```js
const account = state.account;
```
這個重構並不會帶來任何提升,但這是之後改變上的基礎。
This refactoring by itself did not bring much improvements, but the idea was to lay out the foundation for the next changes.
## 追蹤資料改變
現在我們有 `state` 物件儲存資料了,接下來要來中心化這些更新。目標是能輕易地追蹤任何被觸發的改變。
為了避免改動 `state` 物件,我們考慮使它[*不可變*](https://zh.wikipedia.org/wiki/%E4%B8%8D%E5%8F%AF%E8%AE%8A%E7%89%A9%E4%BB%B6),意味著它不能被做任何的修改。
這也代表你必須建立新的狀態物件來替換它。藉由這個方式,你就有一套保護措施阻絕潛在非預期[風險](https://zh.wikipedia.org/wiki/%E5%89%AF%E4%BD%9C%E7%94%A8_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6)),也開創出應用程式內還原與重做的功能,讓程式偵錯更加的容易。舉例來說,你可以紀錄狀態的改變,儲存狀態的歷史紀錄來了解錯誤的來源。
在 JavaScript 中,你可以使用 [`Object.freeze()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) 來建立不可變物件。若你想在不可變物件上做更動,例外處理(exception)就會發生。
✅ 你知道*淺複製(shallow)*和*深複製(deep)*這兩種不可變物件的差別嗎?你可以從[這裡](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze#What_is_shallow_freeze)閱讀相關資訊。
### 課題
我們來建立新的函式 `updateState()`
```js
function updateState(property, newData) {
state = Object.freeze({
...state,
[property]: newData
});
}
```
在這個函式中,我們會建立新的狀態物件,並利用[*展開運算子(`...`)(Spread Operator)*](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Spread_in_object_literals)複製前一個資料狀態。接著,我們使用[括弧記法(Bracket Notation)](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Working_with_Objects#Objects_and_properties) `[property]` 賦予並覆蓋特定的狀態物件。最後,我們為物件上鎖,`Object.freeze()` 避免任何的改動。目前我們只有 `account` 資料存在狀態中,利用此方法可以讓你新增任何你想要的資料。
我們會更新 `state` 初始化設定,確保初始狀態也被上鎖:
```js
let state = Object.freeze({
account: null
});
```
接著,更新函式 `register`,將 `state.account = result;` 替換為:
```js
updateState('account', result);
```
在函式 `login` 上做一樣的事,將 `state.account = data;` 替換為:
```js
updateState('account', data);
```
藉由這個機會,我們能解決帳戶資料在*登出*時,不會被清除的問題。
建立新的函式 `logout()`
```js
function logout() {
updateState('account', null);
navigate('/login');
}
```
`updateDashboard()` 中,替換重新導向 `return navigate('/login');``return logout()`
試著註冊新的帳戶,登入登出以確保功能都運作正常。
> 提示:你可以觀察所有的狀態改變,在 `updateState()` 裡的最下方加入 `console.log(state)`,開啟瀏覽器開發工具,命令欄就會顯示狀態的紀錄。
## 紀錄狀態
多數的網頁應用程式需要儲存資料以確保運作正常。所有重要的資料都會存在資料庫中,並藉由伺服器 API 來存取,就像我們專案中的帳戶資料。但有時候,瀏覽器用戶端的應用程式也需要儲存一些資料,提供更好的使用者體驗與增進負載效能。
當你想在瀏覽器內儲存資料,你必須思考幾項重要的問題:
- *這項資料很危險嗎?* 你應該要避免在用戶端儲存敏感的資料,例如帳戶密碼。
- *你需要儲存資料多久?* 你打算短時間內做存取,還是永久地保存?
網頁應用程式中有許多儲存資訊的方法,一切都取決於你想達成的目標。舉例來說,你可以利用網址來儲存搜尋資訊,讓使用者間能共享資訊。若資料需要與伺服器共享,好比說[認證](https://zh.wikipedia.org/wiki/%E8%BA%AB%E4%BB%BD%E9%AA%8C%E8%AF%81)資訊,你可以使用 [HTTP cookies](https://developer.mozilla.org/docs/Web/HTTP/Cookies)。
另一個選擇是使用其中一個廣大的瀏覽器 API 來儲存資料。下列這兩項就特別有趣:
- [`localStorage`](https://developer.mozilla.org/docs/Web/API/Window/localStorage)[Key/Value 儲存法](https://zh.wikipedia.org/wiki/%E9%94%AE-%E5%80%BC%E5%AD%98%E5%82%A8)可以保存不同時刻的網頁資料。這些資料不會有期限的限制。
- [`sessionStorage`](https://developer.mozilla.org/docs/Web/API/Window/sessionStorage):它的運作模式與 `localStorage` 相同,只差在資料會在網頁段落結束時被清除,如瀏覽器關閉時。
紀錄一下這兩個 API 只能儲存[字串](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)格式。
如果你想儲存更複雜的物件,你需要利用 [`JSON.stringify()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) 將資料整理成 [JSON](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON) 格式。
✅ 如果你想要建立不仰賴伺服器的網頁應用程式,你有辦法在用戶端建立資料庫。[`IndexedDB` API](https://developer.mozilla.org/docs/Web/API/IndexedDB_API) 可以應用在更進階的案例上,儲存更大量的資料,當然使用上也相對複雜。
### 課題
我們想讓使用者在登出之前,保持登入狀態。所以我們使用 `localStorage` 來儲存帳戶資料。首先,定義一組 key 來紀錄我們的資料內容。
```js
const storageKey = 'savedAccount';
```
在函式 `updateState()` 末端加入此行:
```js
localStorage.setItem(storageKey, JSON.stringify(state.account));
```
藉由此方式,帳戶資料就能保存下來,並隨著之前中心化後的狀態而更新。我們開始從之前的重構獲取效益了 🙂。
當資料被儲存後,我們還需要在程式讀取時載入資料。在 `app.js` 下方編寫更多的初始化程式,建立新的函式 `init` 並收入之前的程式碼:
```js
function init() {
const savedAccount = localStorage.getItem(storageKey);
if (savedAccount) {
updateState('account', JSON.parse(savedAccount));
}
// 之前的初始化程式
window.onpopstate = () => updateRoute();
updateRoute();
}
init();
```
我們在此接收了儲存資料,並同步地更新狀態資訊。這必須在更新路由*之前*完成,否則有些程式碼會在頁面更新時,依據狀態來決定其行為。
當儲存完帳戶資料後,我們也定義了*儀表板*頁面為我們的預設首頁。若程式沒有找到資料,儀表板頁面也能重新導向回*登入*頁面。在 `updateRoute()` 中,替換回傳值 `return navigate('/login');``return navigate('/dashboard');`
登入應用程式並重新整理頁面。你應該能維持在儀表板那頁。這個改變也解決了我們最初面臨的問題......
## 重整資料
......但我們可能也產生了新問題。啊呀!
使用 `test` 帳戶進入儀表板頁面,在終端機內執行下列指令以建立新的交易項目:
```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
```
試著重新整理瀏覽器內儀表板頁面。發生了什麼事?你有看到新的交易項目嗎?
感謝 `localStorage` 的幫助,狀態成功的儲存下來,但也代表我們在登出登入之前,不能再改變它的內容了!
一個可能的修復策略是在儀表板載入時,重新載入帳戶資訊以避免資料不同步。
### 課題
建立新的函式 `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);
}
```
這個方法能檢查我們是否已經登入,重新從伺服器載入用戶資料。
建立另一個函式 `refresh`
```js
async function refresh() {
await updateAccountData();
updateDashboard();
}
```
這能更新帳戶資料,更新 HTML 中的儀表板頁面。這是在儀表板路由被載入時,我們所需要呼叫的函式。更新路由定義為:
```js
const routes = {
'/login': { templateId: 'login' },
'/dashboard': { templateId: 'dashboard', init: refresh }
};
```
試著重新載入儀表板,它現在應該能顯示更新後的帳戶資料。
---
## 🚀 挑戰
每一次儀表板載入時,我們都會重新載入帳戶資料,你認為我們還需要在用戶端儲存*所有的帳戶*資料嗎?
試著改變 `localStorage` 內的儲存內容,只包含我們能運行程式的必要資料。
## 課後測驗
[課後測驗](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/48?loc=zh_tw)
## 作業
[編寫"加入交易明細"視窗](assignment.zh-tw.md)
這邊有完成之後的結果:
!["加入交易明細"視窗的例子截圖](../images/dialog.png)

View File

@@ -1,24 +0,0 @@
# Implementar el diálogo "Agregar transacción"
## Instrucciones
A nuestra aplicación bancaria todavía le falta una característica importante: la posibilidad de ingresar nuevas transacciones.
Utilizando todo lo que ha aprendido en las cuatro lecciones anteriores, implemente un cuadro de diálogo "Agregar transacción":
- Agregue un botón "Agregar transacción" en la página del panel
- Cree una nueva página con una plantilla HTML o use JavaScript para mostrar / ocultar el cuadro de diálogo HTML sin salir de la página del tablero (puede usar [`hidden`](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/hidden) para eso, o clases CSS)
- Implementar un formulario HTML para recibir datos de entrada.
- Cree datos JSON a partir de los datos del formulario y envíelos a la API
- Actualiza la página del tablero con los nuevos datos.
Mire las [especificaciones de la API del servidor](../images/dialog.png) para ver a qué API debe llamar y cuál es el formato JSON esperado.
Aquí hay un ejemplo de resultado después de completar la tarea:
! [Captura de pantalla que muestra un ejemplo de diálogo "Agregar transacción"](../images/dialog.png)
## Rúbrica
| Criterios | Ejemplar | Adecuado | Necesita mejorar |
| -------- | -------------------------------------------------- ---------------------------------------------- | -------------------------------------------------- -------------------------------------------------- ------------------- | -------------------------------------------- |
| | La adición de una transacción se implementa completamente siguiendo todas las mejores prácticas que se ven en las lecciones. | Agregar una transacción está implementado, pero no siguiendo las mejores prácticas que se ven en las lecciones, o trabajando solo parcialmente. | Agregar una transacción no funciona en absoluto. |

View File

@@ -1,25 +0,0 @@
# Implémenter la boîte de dialogue "Ajouter une transaction"
## Instructions
Il manque encore à notre application bancaire une fonctionnalité importante: la possibilité de saisir de nouvelles transactions.
En utilisant tout ce que vous avez appris dans les quatre leçons précédentes, implémentez une boîte de dialogue « Ajouter une transaction » :
- Ajouter un bouton "Ajouter une transaction" dans la page du tableau de bord
- Créez une nouvelle page avec un modèle HTML ou utilisez JavaScript pour afficher/masquer le HTML de la boîte de dialogue sans quitter la page du tableau de bord (vous pouvez utiliser [`hidden`](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/hidden) pour celà, ou des classes CSS)
- Assurez-vous de gérer [l'accessibilité du clavier et du lecteur d'écran](https://developer.paciellogroup.com/blog/2018/06/the-current-state-of-modal-dialog-accessibility/) pour la boîte de dialogue
- Implémenter un formulaire HTML pour recevoir les données d'entrée
- Créer des données JSON à partir des données du formulaire et les envoyer à l'API
- Mettre à jour la page du tableau de bord avec les nouvelles données
Consultez les [spécifications de l'API du serveur](./../../api/translations/README.fr.md) pour voir quelle API vous devez appeler et quel est le format JSON attendu.
Voici un exemple de résultat après avoir terminé le devoir :
![Capture d'écran montrant un exemple de boîte de dialogue "Ajouter une transaction"](../images/dialog.png)
## Rubrique
| Critères | Exemplaire | Adéquat | Besoin d'amélioration |
| -------- | ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------|
| | L'ajout d'une transaction est entièrement mis en œuvre en suivant toutes les meilleures pratiques vues dans les leçons. | L'ajout d'une transaction est implémenté, mais ne suit pas les meilleures pratiques vues dans les leçons, ou ne fonctionne que partiellement. | L'ajout d'une transaction ne fonctionne pas du tout. |

View File

@@ -1,25 +0,0 @@
# लेनदेन जोड़ें डियलॉग लागू करें
## अनुदेश
हमारा बैंक ऐप अभी भी एक महत्वपूर्ण विशेषता को याद कर रहा है: नए लेनदेन दर्ज करने की संभावना।
पिछले चार पाठों में आपने जो कुछ भी सीखा है, उसका उपयोग करके "लेनदेन जोड़ें" डियलॉग को लागू करें:
- डैशबोर्ड पृष्ठ में "लेनदेन जोड़ें" बटन जोड़ें
- या तो HTML टेम्पलेट के साथ एक नया पृष्ठ बनाएँ, या डैशबोर्ड पृष्ठ को छोड़े बिना संवाद HTML दिखाने / छिपाने के लिए जावास्क्रिप्ट का उपयोग करें (आप उसके लिए, या CSS कक्षाओं के लिए [`hidden`](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/hidden) हुई संपत्ति का उपयोग कर सकते हैं)
- सुनिश्चित करें कि आप [कीबोर्ड और स्क्रीन रीडर एक्सेसिबिलिटी](https://developer.paciellogroup.com/blog/2018/06/the-current-state-of-modal-dialog-accessibility/) डियलॉग संभालते हैं
- इनपुट डेटा प्राप्त करने के लिए एक HTML फॉर्म को लागू करें
- फॉर्म डेटा से JSON डेटा बनाएं और इसे API पर भेजें
- नए डेटा के साथ डैशबोर्ड पृष्ठ को अपडेट करें
[सर्वर एपीआई विनिर्देशों](../../api/README.md) को देखें कि आपको कौन सा एपीआई कॉल करने की आवश्यकता है और जो JSON प्रारूप अपेक्षित है उसे देखने के लिए।
यहां असाइनमेंट पूरा करने के बाद एक उदाहरण परिणाम है:
![एक उदाहरण "ट्रांसलेशन जोड़ें" डियलॉग दिखाते हुए स्क्रीनशॉट](../images/dialog.png)
## शीर्ष
| मानदंड | उदाहरणात्मक | पर्याप्त | सुधार की जरूरत |
| ------ | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
| | लेन-देन को जोड़ना पाठों में देखी जाने वाली सभी सर्वोत्तम प्रथाओं का पूरी तरह से पालन किया जाता है. | लेन-देन जोड़ना कार्यान्वयन है, लेकिन पाठों में देखी गई सर्वोत्तम प्रथाओं का पालन नहीं करना, या केवल आंशिक रूप से काम करना. | लेनदेन जोड़ना बिल्कुल भी काम नहीं कर रहा है. |

View File

@@ -1,25 +0,0 @@
# Implementare la finestra di dialogo "Aggiungi transazione"
## Istruzioni
All'app bancaria manca ancora una caratteristica importante: la possibilità di inserire nuove transazioni.
Utilizzando tutto quanto appreso nelle quattro lezioni precedenti, implementare una finestra di dialogo "Aggiungi transazione":
- Aggiungere un pulsante "Aggiungi transazione" nella pagina del cruscotto
- Creare una nuova pagina con un modello HTML o usare JavaScript per mostrare/nascondere l'HTML della finestra di dialogo senza lasciare la pagina del cruscotto (si può usare la proprietà [`hidden`](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/hidden) per quello o le classi CSS)
- Assicurarsi di gestire l' [accessibilità dalla tastiera e dal lettore di schermo](https://developer.paciellogroup.com/blog/2018/06/the-current-state-of-modal-dialog-accessibility/) per la finestra di dialogo
- Implementare un form HTML per ricevere i dati di input
- Creare dati JSON dai dati del form e inviarli all'API
- Aggiorna la pagina del cruscotto con i nuovi dati
Guardare [le specifiche dell'API del server](../api/README.md) per vedere quale API si devono chiamare e qual è il formato JSON previsto.
Ecco un esempio di risultato dopo aver completato il compito:
![Videata che mostra un esempio di dialogo "Aggiungi transizione"](../images/dialog.png)
## Rubrica
| Criteri | Ottimo | Adeguato | Necessita miglioramento |
| -------- | ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------|
| | L'aggiunta di una transazione viene implementata seguendo completamente tutte le migliori pratiche viste nelle lezioni. | L'aggiunta di una transazione è implementata, ma non seguendo le migliori pratiche viste nelle lezioni o funzionante solo parzialmente. | L'aggiunta di una transazione non funziona affatto. |

View File

@@ -1,25 +0,0 @@
# 「トランザクションの追加」ダイアログの実装
## 説明書
私たちの銀行アプリには、1つの重要な機能がまだありません。
前の4つのレッスンで学んだことをすべて使って、「トランザクションの追加」ダイアログを実装します。
- ダッシュボードページに「トランザクションの追加」ボタンを追加します
- HTML テンプレートで新しいページを作成するか、JavaScript を使用してダッシュボード・ページを離れることなくダイアログの HTML を表示/非表示にするかのいずれかを選択します (そのためには [`hidden`](https://developer.mozilla.org/ja/docs/Web/HTML/Global_attributes/hidden) プロパティを使用するか、CSS クラスを使用することができます)
- ダイアログの [キーボードとスクリーンリーダーのアクセシビリティ] (https://developer.paciellogroup.com/blog/2018/06/the-current-state-of-modal-dialog-accessibility/) が処理されていることを確認します
- 入力データを受け取るための HTML フォームを実装する
- フォームデータから JSON データを作成して API に送る
- ダッシュボードページを新しいデータで更新する
[サーバー API の仕様](./.../.../api/translations/README.ja.md)を見て、どの API を呼び出す必要があるか、期待される JSON 形式は何かを確認してください。
以下は、課題を完了した後の結果の例です。
![「トランジションの追加」ダイアログの例を示すスクリーンショット](../images/dialog.png)
## ルーブリック
| 基準 | 模範的な例 | 適切な | 改善が必要 |
| -------- | ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------|
| | トランザクションの追加は、レッスンで見たすべてのベストプラクティスに従って完全に実装されています。 | トランザクションを追加することは実装されていますが、レッスンで見たベストプラクティスに従っていなかったり、部分的にしか動作しなかったりします。 | トランザクションを追加しても全く機能しません。 |

View File

@@ -1,24 +0,0 @@
# "거래 추가" 대화 상자 구현
## 설명
우리 은행 앱에는 여전히 새로운 거래를 입력 할 수 있는 한 가지 중요한 기능이 없습니다. 이전 네 가지 수업에서 배운 모든 것을 사용하여 "트랜잭션 추가" 대화 상자를 구현해봅시다.
- 대시 보드 페이지에 "거래 추가" 버튼 추가
- HTML 템플릿으로 새 페이지를 만들거나 JavaScript를 사용하여 대시 보드 페이지를 벗어나지 않고 대화 상자 HTML을 표시하거나 숨깁니다(해당 항목에 대해 [`hidden`](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/hidden) 속성 또는 CSS 클래스를 사용할 수 있음).
- 대화 상자에 대한 [키보드 및 화면 판독기 접근성](https://developer.paciellogroup.com/blog/2018/06/the-current-state-of-modal-dialog-accessibility/)을 처리해야합니다.
- 입력 데이터 수신을 위한 HTML 양식 구현
- 양식 데이터에서 JSON 데이터를 만들어 API로 보냅니다.
- 새 데이터로 대시 보드 페이지 업데이트
호출해야하는 API와 예상되는 JSON 형식을 확인하려면 [서버 API 사양](../api/README.md) 을 살펴보세요.
다음은 과제를 완료 한 후의 예시 결과입니다.
![예시를 보여주는 스크린샷 이미지](./images/dialog.png)
## 평가 기준
기준 | 모범 답안 | 적당한 답안 | 개선이 필요한 답안
--- | --- | --- | ---
| 거래 추가 기능이 수업에서 본 모든 모범 사례에 따라 완전히 구현된 경우 | 거래 추가기능은 구현되지만, 강의에 표시된 모범 사례를 따르지 않거나 부분적으로만 작동하는 경우 | 거래 추가기능이 전혀 동작하지 않는 경우

View File

@@ -1,25 +0,0 @@
# Laksanakan dialog "Tambah transaksi"
## Arahan
Aplikasi bank kami masih belum mempunyai satu ciri penting: kemungkinan untuk melakukan transaksi baru.
Menggunakan semua yang telah anda pelajari dalam empat pelajaran sebelumnya, laksanakan dialog "Tambah transaksi":
- Tambahkan butang "Tambah transaksi" di halaman papan pemuka
- Buat halaman baru dengan templat HTML, atau gunakan JavaScript untuk menunjukkan / menyembunyikan HTML dialog tanpa meninggalkan halaman papan pemuka (anda boleh menggunakan [`hidden`](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/hidden) property for that, or CSS classes) untuk itu, atau kelas CSS)
- Pastikan anda mengendalikan [kebolehcapaian pembaca papan kekunci dan skrin](https://developer.paciellogroup.com/blog/2018/06/the-current-state-of-modal-dialog-accessibility/) untuk dialog
- Terapkan borang HTML untuk menerima data input
- Buat data JSON dari data formulir dan kirimkan ke API
- Kemas kini halaman papan pemuka dengan data baru
Lihat [server API specifications](../../api/README.ms.md) untuk melihat API mana yang perlu anda panggil dan format JSON yang diharapkan.
Inilah hasil contoh setelah menyelesaikan tugasan:
![Tangkapan skrin yang menunjukkan contoh dialog "Tambahkan peralihan"](../images/dialog.png)
## Rubrik
| Kriteria | Contoh | Mencukupi | Usaha Lagi |
| -------- | ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------|
| | Menambah transaksi dilaksanakan sepenuhnya mengikuti semua amalan terbaik yang dilihat dalam pelajaran. | Menambah urus niaga dilaksanakan, tetapi tidak mengikuti amalan terbaik yang dapat dilihat dalam pelajaran, atau hanya bekerja sebahagian. | Menambah urus niaga tidak berfungsi sama sekali. |

View File

@@ -1,25 +0,0 @@
# Implementeer het dialoogvenster "Transactie toevoegen"
## Instructies
Onze bank-app mist nog één belangrijk kenmerk: de mogelijkheid om nieuwe transacties in te voeren.
Gebruik alles wat u in de vier voorgaande lessen heeft geleerd en implementeer een dialoogvenster "Transactie toevoegen":
- Voeg een knop "Transactie toevoegen" toe op de dashboardpagina
- Maak een nieuwe pagina met een HTML-sjabloon of gebruik JavaScript om de HTML-dialoog weer te geven/te verbergen zonder de dashboardpagina te verlaten (u kunt [`hidden`](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/hidden) eigenschap daarvoor gebruiken, of CSS-class)
- Zorg ervoor dat u [toetsenbord en schermlezer toegankelijkheid](https://developer.paciellogroup.com/blog/2018/06/the-current-state-of-modal-dialog-accessibility/) afhandelt voor het dialoogvenster
- Implementeer een HTML-formulier om invoergegevens te ontvangen
- Maak JSON-gegevens van de formuliergegevens en stuur deze naar de API
- Werk de dashboardpagina bij met de nieuwe gegevens
Kijk naar de [server API-specificaties](../api/README.md) om te zien welke API u moet aanroepen en wat het verwachte JSON-formaat is.
Hier is een voorbeeldresultaat na het voltooien van de opdracht:
![Schermafbeelding met een voorbeeld "Transactie toevoegen" dialoogvenster](../images/dialog.png)
## Rubriek
| Criteria | Voorbeeldig | Voldoende | Moet worden verbeterd |
| -------- | ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------|
| | Het toevoegen van een transactie wordt volledig geïmplementeerd volgens alle best practices uit de lessen. | Het toevoegen van een transactie is geïmplementeerd, maar volgt niet de best practices uit de lessen, of werkt slechts gedeeltelijk. | Het toevoegen van een transactie werkt helemaal niet. |

View File

@@ -1,25 +0,0 @@
# 編寫"加入交易明細"視窗
## 簡介
我們的銀行應用程式還缺乏一項重要的功能:輸入新的交易明細。
使用你在這四堂課中學到的知識,編寫"加入交易明細"視窗:
- 在儀表板頁面新增"加入交易明細"按鈕
- 加入新的 HTML 模板建立新頁面,或是在同一頁面中使用 JavaScript 顯示 HTML 窗格(可以使用 [`hidden`](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/hidden) 屬性,或是 CSS classes)
- 確保視窗能滿足[鍵盤與螢幕報讀器的相容性](https://developer.paciellogroup.com/blog/2018/06/the-current-state-of-modal-dialog-accessibility/)
- 編寫 HTML 表單來接收輸入資料
- 建立 JSON 表單資料並傳送到 API 上
- 使用新資料更新到儀表板頁面上
看看[伺服器 API 規格](../../api/README.zh-tw.md)來查詢你需要呼叫的 API 和所需的 JSON 格式。
這邊有完成作業後的成果:
!["加入交易明細"視窗的例子截圖](../images/dialog.png)
## 學習評量
| 作業內容 | 優良 | 普通 | 待改進 |
| -------- | ------------------------------------ | -------------------------------------------------- | ---------------------- |
| | 利用課程內容完美的製作出交易明細功能 | 有製作出交易明細功能,但有缺少部分要點且功能不完全 | 新的交易明細功能不正常 |