17 KiB
Gumawa ng Banking App Bahagi 4: Mga Konsepto ng State Management
Pre-Lecture Quiz
Panimula
Habang lumalaki ang isang web application, nagiging hamon ang pagsubaybay sa lahat ng daloy ng data. Aling code ang kumukuha ng data, anong pahina ang gumagamit nito, saan at kailan ito kailangang i-update...madaling magresulta sa magulong code na mahirap i-maintain. Lalo na kung kailangan mong magbahagi ng data sa iba't ibang pahina ng iyong app, tulad ng user data. Ang konsepto ng state management ay matagal nang umiiral sa lahat ng uri ng programa, ngunit habang patuloy na lumalaki ang pagiging kumplikado ng mga web app, ito ay naging mahalagang aspeto na dapat isaalang-alang sa panahon ng development.
Sa huling bahaging ito, susuriin natin ang app na ginawa natin upang muling pag-isipan kung paano pinamamahalaan ang state, na nagbibigay-daan sa suporta para sa pag-refresh ng browser sa anumang punto, at pagpapanatili ng data sa mga session ng user.
Paunang Kailangan
Kailangan mong matapos ang data fetching na bahagi ng web app para sa araling ito. Kailangan mo ring i-install ang Node.js at patakbuhin ang server API nang lokal upang ma-manage ang account data.
Maaari mong subukan kung gumagana nang maayos ang server sa pamamagitan ng pag-execute ng command na ito sa terminal:
curl http://localhost:5000/api
# -> should return "Bank API v1.0.0" as a result
Muling Pag-isipan ang State Management
Sa nakaraang aralin, ipinakilala natin ang pangunahing konsepto ng state sa ating app gamit ang global na account
variable na naglalaman ng bank data para sa kasalukuyang naka-login na user. Gayunpaman, ang kasalukuyang implementasyon natin ay may ilang mga kahinaan. Subukan mong i-refresh ang pahina habang nasa dashboard ka. Ano ang nangyayari?
Mayroong 3 isyu sa kasalukuyang code:
- Ang state ay hindi napapanatili, dahil ang pag-refresh ng browser ay ibabalik ka sa login page.
- Maraming mga function ang nagbabago sa state. Habang lumalaki ang app, maaaring maging mahirap subaybayan ang mga pagbabago at madaling makalimutan ang pag-update ng isa.
- Ang state ay hindi nalilinis, kaya kapag nag-click ka sa Logout, ang account data ay nananatili kahit na nasa login page ka na.
Maaari nating i-update ang ating code upang harapin ang mga isyung ito isa-isa, ngunit magreresulta ito sa mas maraming code duplication at gagawing mas kumplikado at mahirap i-maintain ang app. O maaari tayong mag-pause ng ilang minuto at muling pag-isipan ang ating estratehiya.
Anong mga problema ang talagang sinusubukan nating lutasin dito?
Ang State management ay tungkol sa paghahanap ng magandang paraan upang lutasin ang dalawang partikular na problema:
- Paano mapapanatili ang daloy ng data sa isang app na madaling maunawaan?
- Paano mapapanatili ang state data na palaging naka-sync sa user interface (at vice versa)?
Kapag naayos mo na ang mga ito, ang anumang iba pang isyu na maaaring mayroon ka ay maaaring nalutas na o naging mas madali nang lutasin. Maraming posibleng paraan para lutasin ang mga problemang ito, ngunit gagamit tayo ng karaniwang solusyon na binubuo ng pag-centralize ng data at ng mga paraan para baguhin ito. Ang daloy ng data ay magiging ganito:
Hindi natin tatalakayin dito ang bahagi kung saan ang data ay awtomatikong nagti-trigger ng view update, dahil ito ay konektado sa mas advanced na mga konsepto ng Reactive Programming. Magandang follow-up na paksa ito kung handa kang mag-dive nang mas malalim.
✅ Maraming mga library ang may iba't ibang approach sa state management, Redux ang isa sa mga sikat na opsyon. Tingnan ang mga konsepto at pattern na ginagamit nito dahil madalas itong magandang paraan upang matutunan ang mga potensyal na isyu na maaaring harapin sa malalaking web app at kung paano ito malulutas.
Gawain
Magsisimula tayo sa kaunting refactoring. Palitan ang deklarasyon ng account
:
let account = null;
Gamit:
let state = {
account: null
};
Ang ideya ay i-centralize ang lahat ng data ng ating app sa isang state object. Sa ngayon, mayroon lamang tayong account
sa state kaya hindi ito masyadong nagbabago, ngunit nagbibigay ito ng daan para sa mga susunod na pagbabago.
Kailangan din nating i-update ang mga function na gumagamit nito. Sa mga function na register()
at login()
, palitan ang account = ...
ng state.account = ...
;
Sa itaas ng function na updateDashboard()
, idagdag ang linyang ito:
const account = state.account;
Ang refactoring na ito ay hindi nagdala ng maraming improvement, ngunit ang ideya ay maglatag ng pundasyon para sa mga susunod na pagbabago.
Subaybayan ang Mga Pagbabago sa Data
Ngayon na nailagay na natin ang state
object para mag-imbak ng ating data, ang susunod na hakbang ay i-centralize ang mga update. Ang layunin ay gawing mas madali ang pagsubaybay sa anumang mga pagbabago at kung kailan ito nangyayari.
Upang maiwasan ang mga pagbabago sa state
object, magandang practice din na ituring itong immutable, ibig sabihin hindi ito maaaring baguhin. Nangangahulugan din ito na kailangan mong gumawa ng bagong state object kung gusto mong baguhin ang anumang bagay dito. Sa paggawa nito, nagtatayo ka ng proteksyon laban sa mga posibleng hindi gustong side effects, at nagbubukas ng mga posibilidad para sa mga bagong feature sa iyong app tulad ng pag-implement ng undo/redo, habang pinapadali rin ang pag-debug. Halimbawa, maaari mong i-log ang bawat pagbabago na ginawa sa state at panatilihin ang history ng mga pagbabago upang maunawaan ang pinagmulan ng isang bug.
Sa JavaScript, maaari mong gamitin ang Object.freeze()
upang lumikha ng immutable na bersyon ng isang object. Kung susubukan mong gumawa ng mga pagbabago sa isang immutable object, magri-raise ng exception.
✅ Alam mo ba ang pagkakaiba ng shallow at deep immutable object? Maaari mong basahin ito dito.
Gawain
Gumawa tayo ng bagong updateState()
function:
function updateState(property, newData) {
state = Object.freeze({
...state,
[property]: newData
});
}
Sa function na ito, gumagawa tayo ng bagong state object at kinokopya ang data mula sa nakaraang state gamit ang spread (...
) operator. Pagkatapos, pinapalitan natin ang partikular na property ng state object gamit ang bracket notation [property]
para sa assignment. Sa wakas, nilalock natin ang object upang maiwasan ang mga pagbabago gamit ang Object.freeze()
. Sa ngayon, mayroon lamang tayong account
property na naka-store sa state, ngunit sa approach na ito maaari kang magdagdag ng maraming property hangga't kailangan mo sa state.
I-update din natin ang state
initialization upang matiyak na ang initial state ay frozen din:
let state = Object.freeze({
account: null
});
Pagkatapos nito, i-update ang register
function sa pamamagitan ng pagpapalit ng state.account = result;
assignment ng:
updateState('account', result);
Gawin din ito sa login
function, palitan ang state.account = data;
ng:
updateState('account', data);
Ngayon, samantalahin natin ang pagkakataon upang ayusin ang isyu ng account data na hindi nalilinis kapag nag-click ang user sa Logout.
Gumawa ng bagong function na logout()
:
function logout() {
updateState('account', null);
navigate('/login');
}
Sa updateDashboard()
, palitan ang redirection na return navigate('/login');
ng return logout()
;
Subukang magrehistro ng bagong account, mag-logout, at mag-login muli upang suriin kung gumagana pa rin nang maayos ang lahat.
Tip: Maaari mong tingnan ang lahat ng pagbabago sa state sa pamamagitan ng pagdaragdag ng
console.log(state)
sa ibaba ngupdateState()
at pagbukas ng console sa development tools ng iyong browser.
Panatilihin ang State
Karamihan sa mga web app ay kailangang magpanatili ng data upang gumana nang maayos. Ang lahat ng critical na data ay karaniwang naka-store sa isang database at ina-access sa pamamagitan ng server API, tulad ng user account data sa ating kaso. Ngunit minsan, interesante rin na magpanatili ng ilang data sa client app na tumatakbo sa iyong browser, para sa mas magandang user experience o upang mapabuti ang loading performance.
Kapag gusto mong magpanatili ng data sa iyong browser, may ilang mahahalagang tanong na dapat mong itanong sa iyong sarili:
- Sensitive ba ang data? Dapat mong iwasan ang pag-store ng anumang sensitive na data sa client, tulad ng mga password ng user.
- Gaano katagal mo kailangang panatilihin ang data na ito? Plano mo bang i-access ang data na ito para lamang sa kasalukuyang session o gusto mo itong i-store magpakailanman?
Mayroong iba't ibang paraan ng pag-store ng impormasyon sa loob ng isang web app, depende sa kung ano ang gusto mong makamit. Halimbawa, maaari mong gamitin ang URLs upang mag-store ng search query, at gawing shareable ito sa pagitan ng mga user. Maaari mo ring gamitin ang HTTP cookies kung ang data ay kailangang i-share sa server, tulad ng authentication information.
Isa pang opsyon ay ang paggamit ng isa sa maraming browser APIs para sa pag-store ng data. Dalawa sa mga ito ang partikular na interesante:
localStorage
: isang Key/Value store na nagpapahintulot sa pagpanatili ng data na partikular sa kasalukuyang website sa iba't ibang session. Ang data na naka-save dito ay hindi kailanman nag-e-expire.sessionStorage
: ito ay gumagana nang katulad salocalStorage
maliban na ang data na naka-store dito ay nalilinis kapag natapos ang session (kapag isinara ang browser).
Tandaan na ang parehong APIs na ito ay nagpapahintulot lamang sa pag-store ng strings. Kung gusto mong mag-store ng complex objects, kailangan mo itong i-serialize sa JSON format gamit ang JSON.stringify()
.
✅ Kung gusto mong gumawa ng web app na hindi gumagana sa server, posible rin na gumawa ng database sa client gamit ang IndexedDB
API. Ang API na ito ay nakalaan para sa advanced na paggamit o kung kailangan mong mag-store ng malaking dami ng data, dahil mas kumplikado itong gamitin.
Gawain
Gusto natin na ang mga user ay manatiling naka-login hanggang sa sila mismo ang mag-click sa Logout button, kaya gagamit tayo ng localStorage
upang i-store ang account data. Una, mag-define tayo ng key na gagamitin natin para i-store ang ating data.
const storageKey = 'savedAccount';
Pagkatapos, idagdag ang linyang ito sa dulo ng updateState()
function:
localStorage.setItem(storageKey, JSON.stringify(state.account));
Sa ganitong paraan, ang user account data ay mapapanatili at palaging up-to-date dahil na-centralize natin ang lahat ng ating state updates. Dito natin sisimulan ang pagkuha ng benepisyo mula sa lahat ng ating mga naunang refactor 🙂.
Dahil ang data ay naka-save, kailangan din nating alagaan ang pag-restore nito kapag ang app ay na-load. Dahil magsisimula tayong magkaroon ng mas maraming initialization code, magandang ideya na gumawa ng bagong init
function, na kasama rin ang ating naunang code sa ibaba ng app.js
:
function init() {
const savedAccount = localStorage.getItem(storageKey);
if (savedAccount) {
updateState('account', JSON.parse(savedAccount));
}
// Our previous initialization code
window.onpopstate = () => updateRoute();
updateRoute();
}
init();
Dito natin kinukuha ang naka-save na data, at kung mayroon, ina-update natin ang state nang naaayon. Mahalagang gawin ito bago i-update ang route, dahil maaaring may code na umaasa sa state sa panahon ng page update.
Maaari rin nating gawing default page ng ating application ang Dashboard page, dahil ngayon ay pinapanatili na natin ang account data. Kung walang data na natagpuan, ang dashboard ang bahala sa pag-redirect sa Login page. Sa updateRoute()
, palitan ang fallback na return navigate('/login');
ng return navigate('/dashboard');
.
Ngayon mag-login sa app at subukang i-refresh ang pahina. Dapat kang manatili sa dashboard. Sa update na ito, naayos na natin ang lahat ng ating mga paunang isyu...
I-refresh ang Data
...Ngunit maaaring nakagawa rin tayo ng bagong isyu. Oops!
Pumunta sa dashboard gamit ang test
account, pagkatapos ay patakbuhin ang command na ito sa terminal upang gumawa ng bagong transaction:
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
Subukang i-refresh ang dashboard page sa browser ngayon. Ano ang nangyayari? Nakikita mo ba ang bagong transaction?
Ang state ay napapanatili nang walang hanggan salamat sa localStorage
, ngunit nangangahulugan din ito na hindi ito kailanman na-update hanggang sa mag-log out ka sa app at mag-log in muli!
Isang posibleng estratehiya upang ayusin ito ay ang pag-reload ng account data tuwing na-load ang dashboard, upang maiwasan ang stale data.
Gawain
Gumawa ng bagong function na updateAccountData
:
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);
}
Ang method na ito ay nagche-check kung kasalukuyan kang naka-login, pagkatapos ay nire-reload ang account data mula sa server.
Gumawa ng isa pang function na tinatawag na refresh
:
async function refresh() {
await updateAccountData();
updateDashboard();
}
Ang function na ito ay nag-a-update ng account data, pagkatapos ay ina-update ang HTML ng dashboard page. Ito ang kailangan nating tawagin kapag na-load ang dashboard route. I-update ang route definition gamit:
const routes = {
'/login': { templateId: 'login' },
'/dashboard': { templateId: 'dashboard', init: refresh }
};
Subukang i-refresh ang dashboard ngayon, dapat nitong ipakita ang na-update na account data.
🚀 Hamon
Ngayon na nire-reload natin ang account data tuwing na-load ang dashboard, sa tingin mo ba kailangan pa nating panatilihin lahat ng account data?
Subukang magtulungan upang baguhin kung ano ang sine-save at niloload mula sa localStorage
upang isama lamang ang mga bagay na talagang kinakailangan para gumana ang app.
Post-Lecture Quiz
Takdang-Aralin
Ipapatupad ang "Magdagdag ng transaksyon" na dialog
Narito ang isang halimbawa ng resulta matapos makumpleto ang gawain:
Paunawa:
Ang dokumentong ito ay isinalin gamit ang AI translation service na Co-op Translator. Bagama't sinisikap naming maging tumpak, tandaan na ang mga awtomatikong pagsasalin ay maaaring maglaman ng mga pagkakamali o hindi pagkakatugma. Ang orihinal na dokumento sa kanyang katutubong wika ang dapat ituring na opisyal na sanggunian. Para sa mahalagang impormasyon, inirerekomenda ang propesyonal na pagsasalin ng tao. Hindi kami mananagot sa anumang hindi pagkakaunawaan o maling interpretasyon na dulot ng paggamit ng pagsasaling ito.