# बँकिंग अ‍ॅप तयार करा भाग ४: स्टेट मॅनेजमेंटची संकल्पना ## प्री-लेक्चर क्विझ [प्री-लेक्चर क्विझ](https://ff-quizzes.netlify.app/web/quiz/47) ### परिचय वेब अ‍ॅप्लिकेशन मोठे होत असताना, डेटा फ्लो ट्रॅक करणे आव्हानात्मक बनते. कोणता कोड डेटा मिळवतो, कोणते पृष्ठ तो वापरते, तो कधी आणि कुठे अपडेट करायचा... हे सर्व व्यवस्थित ठेवणे कठीण होऊ शकते. विशेषतः जेव्हा तुम्हाला तुमच्या अ‍ॅपच्या वेगवेगळ्या पृष्ठांमध्ये डेटा शेअर करायचा असतो, जसे की वापरकर्त्याचा डेटा. *स्टेट मॅनेजमेंट* ही संकल्पना सर्व प्रकारच्या प्रोग्राम्समध्ये नेहमीच अस्तित्वात असते, परंतु वेब अ‍ॅप्स अधिक जटिल होत असल्याने विकासादरम्यान यावर विचार करणे महत्त्वाचे बनले आहे. या अंतिम भागात, आपण तयार केलेल्या अ‍ॅपवर पुनर्विचार करू आणि स्टेट कसे व्यवस्थापित केले जाते ते पाहू, ब्राउझर रिफ्रेशला कोणत्याही वेळी समर्थन देणे आणि वापरकर्ता सत्रांमध्ये डेटा टिकवून ठेवणे यासाठी. ### पूर्वतयारी या धड्यासाठी तुम्ही वेब अ‍ॅपचा [डेटा फेचिंग](../3-data/README.md) भाग पूर्ण केला असावा. तुम्हाला [Node.js](https://nodejs.org) इंस्टॉल करणे आणि [सर्व्हर API](../api/README.md) स्थानिक पातळीवर चालवणे आवश्यक आहे जेणेकरून तुम्ही खाते डेटा व्यवस्थापित करू शकता. तुम्ही टर्मिनलमध्ये खालील कमांड चालवून सर्व्हर योग्यरित्या चालत आहे का ते तपासू शकता: ```sh curl http://localhost:5000/api # -> should return "Bank API v1.0.0" as a result ``` --- ## स्टेट मॅनेजमेंटवर पुनर्विचार करा [मागील धडा](../3-data/README.md) मध्ये, आम्ही `account` नावाच्या ग्लोबल व्हेरिएबलसह अ‍ॅपमध्ये स्टेटची मूलभूत संकल्पना सादर केली होती, ज्यामध्ये सध्या लॉग इन केलेल्या वापरकर्त्याचा बँक डेटा आहे. परंतु, आमच्या सध्याच्या अंमलबजावणीत काही त्रुटी आहेत. डॅशबोर्डवर असताना पृष्ठ रिफ्रेश करून पहा. काय होते? सध्याच्या कोडमध्ये ३ समस्या आहेत: - स्टेट टिकवून ठेवले जात नाही, कारण ब्राउझर रिफ्रेश तुम्हाला लॉगिन पृष्ठावर परत नेते. - स्टेट बदलण्यासाठी अनेक फंक्शन्स आहेत. अ‍ॅप मोठा होत असताना, बदल ट्रॅक करणे कठीण होऊ शकते आणि एखादा अपडेट विसरणे सोपे होऊ शकते. - स्टेट साफ केला जात नाही, त्यामुळे तुम्ही *लॉगआउट* क्लिक केल्यावरही खाते डेटा तिथेच राहतो, जरी तुम्ही लॉगिन पृष्ठावर असाल. आम्ही या समस्यांना एक-एक करून सोडवण्यासाठी आमचा कोड अपडेट करू शकतो, परंतु यामुळे कोड डुप्लिकेशन वाढेल आणि अ‍ॅप अधिक जटिल आणि देखभाल करणे कठीण होईल. किंवा आम्ही काही मिनिटे थांबून आमची रणनीती पुन्हा विचार करू शकतो. > आम्ही येथे कोणत्या समस्यांचे निराकरण करण्याचा प्रयत्न करत आहोत? [स्टेट मॅनेजमेंट](https://en.wikipedia.org/wiki/State_management) म्हणजे या दोन विशिष्ट समस्यांचे निराकरण करण्यासाठी चांगला दृष्टिकोन शोधणे: - अ‍ॅपमधील डेटा फ्लो समजण्यास सोपे कसे ठेवायचे? - स्टेट डेटा नेहमी वापरकर्ता इंटरफेससह (आणि उलट) समक्रमित कसा ठेवायचा? एकदा तुम्ही याची काळजी घेतली की, तुम्हाला असलेल्या इतर कोणत्याही समस्यांचे निराकरण आधीच झाले असेल किंवा सोडवणे सोपे झाले असेल. या समस्यांचे निराकरण करण्यासाठी अनेक संभाव्य दृष्टिकोन आहेत, परंतु आम्ही **डेटा आणि त्यात बदल करण्याचे मार्ग केंद्रीकृत करणे** या सामान्य उपायाचा अवलंब करू. डेटा फ्लो असे असेल: ![HTML, वापरकर्ता क्रिया आणि स्टेट यामधील डेटा फ्लो दर्शवणारी योजना](../../../../translated_images/data-flow.fa2354e0908fecc89b488010dedf4871418a992edffa17e73441d257add18da4.mr.png) > आम्ही येथे डेटा स्वयंचलितपणे दृश्य अपडेट ट्रिगर करतो तो भाग कव्हर करणार नाही, कारण तो [Reactive Programming](https://en.wikipedia.org/wiki/Reactive_programming) च्या अधिक प्रगत संकल्पनांशी संबंधित आहे. तुम्हाला सखोल अभ्यास करायचा असेल तर हा एक चांगला पुढील विषय आहे. ✅ स्टेट मॅनेजमेंटसाठी वेगवेगळ्या दृष्टिकोनांसह अनेक लायब्ररी उपलब्ध आहेत, [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; ``` या रिफॅक्टरिंगमुळे फारसा सुधारणा झाली नाही, परंतु पुढील बदलांसाठी पाया तयार करण्याचा उद्देश होता. ## डेटा बदल ट्रॅक करा आता आपण डेटा स्टोअर करण्यासाठी `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) वापरून ऑब्जेक्टची immutable आवृत्ती तयार करता येते. जर तुम्ही immutable ऑब्जेक्टमध्ये बदल करण्याचा प्रयत्न केला तर अपवाद निर्माण होईल. ✅ तुम्हाला *shallow* आणि *deep* immutable ऑब्जेक्टमधील फरक माहित आहे का? तुम्ही याबद्दल [इथे](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 द्वारे प्रवेश केला जातो, जसे की आमच्या बाबतीत वापरकर्ता खाते डेटा. परंतु कधीकधी, ब्राउझरमध्ये चालणाऱ्या क्लायंट अ‍ॅपवर काही डेटा टिकवून ठेवणे देखील मनोरंजक असते, चांगल्या वापरकर्ता अनुभवासाठी किंवा लोडिंग कार्यक्षमता सुधारण्यासाठी. तुम्हाला तुमच्या ब्राउझरमध्ये डेटा टिकवून ठेवायचा असल्यास, तुम्ही स्वतःला काही महत्त्वाचे प्रश्न विचारले पाहिजेत: - *डेटा संवेदनशील आहे का?* तुम्ही क्लायंटवर कोणताही संवेदनशील डेटा संग्रहित करण्याचे टाळले पाहिजे, जसे की वापरकर्ता पासवर्ड. - *तुम्हाला किती काळासाठी हा डेटा ठेवायचा आहे?* तुम्ही हा डेटा फक्त चालू सत्रासाठी प्रवेश करण्याची योजना आखत आहात की तुम्हाला तो कायमस्वरूपी संग्रहित करायचा आहे? वेब अ‍ॅपमध्ये माहिती संग्रहित करण्याचे अनेक मार्ग आहेत, तुम्हाला काय साध्य करायचे आहे यावर अवलंबून. उदाहरणार्थ, तुम्ही URL वापरून शोध क्वेरी संग्रहित करू शकता आणि ती वापरकर्त्यांमध्ये शेअर करण्यायोग्य बनवू शकता. तुम्ही [HTTP cookies](https://developer.mozilla.org/docs/Web/HTTP/Cookies) देखील वापरू शकता जर डेटा सर्व्हरसह शेअर करणे आवश्यक असेल, जसे की [authentication](https://en.wikipedia.org/wiki/Authentication) माहिती. डेटा संग्रहित करण्यासाठी अनेक ब्राउझर APIs आहेत, त्यापैकी दोन विशेषतः मनोरंजक आहेत: - [`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` प्रमाणेच कार्य करते परंतु यात संग्रहित डेटा सत्र संपल्यावर (ब्राउझर बंद झाल्यावर) साफ केला जातो. लक्षात घ्या की या दोन्ही APIs फक्त [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) वापरून क्लायंटवर डेटाबेस तयार करणे देखील शक्य आहे. हे प्रगत उपयोग प्रकरणांसाठी किंवा तुम्हाला मोठ्या प्रमाणात डेटा संग्रहित करायचा असल्यास राखीव आहे, कारण ते वापरण्यास अधिक जटिल आहे. ### कार्य आम्हाला आमच्या वापरकर्त्यांना *लॉगआउट* बटणावर स्पष्टपणे क्लिक करेपर्यंत लॉग इन ठेवायचे आहे, त्यामुळे आम्ही खाते डेटा संग्रहित करण्यासाठी `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)); } // 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 } }; ``` आता डॅशबोर्ड रिफ्रेश करून पहा, ते अपडेट केलेला खाते डेटा प्रदर्शित करेल. --- ## 🚀 आव्हान आता आपण डॅशबोर्ड लोड झाल्यावर प्रत्येक वेळी खाते डेटा पुन्हा लोड करतो, तुम्हाला असे वाटते का की आपल्याला [व्याख्यानानंतरचा प्रश्नमंजूषा](https://ff-quizzes.netlify.app/web/quiz/48) ## असाइनमेंट ["Add transaction" संवाद लागू करा](assignment.md) असाइनमेंट पूर्ण केल्यानंतरचा एक उदाहरण परिणाम येथे दिला आहे: !["Add transaction" संवादाचे उदाहरण स्क्रीनशॉट दाखवत आहे](../../../../translated_images/dialog.93bba104afeb79f12f65ebf8f521c5d64e179c40b791c49c242cf15f7e7fab15.mr.png) **अस्वीकरण**: हा दस्तऐवज AI भाषांतर सेवा [Co-op Translator](https://github.com/Azure/co-op-translator) वापरून भाषांतरित करण्यात आला आहे. आम्ही अचूकतेसाठी प्रयत्नशील असलो तरी कृपया लक्षात ठेवा की स्वयंचलित भाषांतरांमध्ये त्रुटी किंवा अचूकतेचा अभाव असू शकतो. मूळ भाषेतील दस्तऐवज हा अधिकृत स्रोत मानला जावा. महत्त्वाच्या माहितीसाठी व्यावसायिक मानवी भाषांतराची शिफारस केली जाते. या भाषांतराचा वापर करून उद्भवलेल्या कोणत्याही गैरसमज किंवा चुकीच्या अर्थासाठी आम्ही जबाबदार राहणार नाही.