# एक बैंकिंग ऐप का निर्माण करें भाग 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)
