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 mauwi sa magulong code na mahirap i-maintain. Lalo na kung kailangan mong ibahagi ang data sa iba't ibang pahina ng iyong app, tulad ng user data. Ang konsepto ng state management ay palaging umiiral sa lahat ng uri ng programa, ngunit habang patuloy na lumalago ang pagiging kumplikado ng mga web app, ito ay nagiging 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 iba't ibang session ng user.
Prerequisite
Kailangan mong makumpleto ang bahagi ng data fetching 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 maayos na tumatakbo 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 isang pangunahing konsepto ng state sa ating app gamit ang global na account
variable na naglalaman ng bank data para sa kasalukuyang naka-log in na user. Gayunpaman, ang kasalukuyang implementasyon natin ay may ilang mga kakulangan. Subukang 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 ibinabalik ka sa login page.
- Maraming mga function ang nagmo-modify ng state. Habang lumalaki ang app, nagiging mahirap itong subaybayan at madaling makalimutan ang pag-update ng isa.
- Ang state ay hindi nalilinis, kaya kapag nag-click ka sa Logout, ang account data ay naroon pa rin kahit na nasa login page ka na.
Maaari nating i-update ang ating code upang harapin ang mga isyung ito isa-isa, ngunit ito ay magdudulot ng mas maraming code duplication at gagawing mas kumplikado at mahirap i-maintain ang app. O maaari tayong huminto 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 problemang ito:
- Paano panatilihing madaling maunawaan ang daloy ng data sa isang app?
- Paano panatilihing naka-sync ang state data sa user interface (at vice versa)?
Kapag naayos mo na ang mga ito, ang anumang iba pang isyu na maaaring mayroon ka ay maaaring naayos na o naging mas madali nang ayusin. Maraming posibleng paraan upang lutasin ang mga problemang ito, ngunit gagamit tayo ng isang karaniwang solusyon na binubuo ng pagse-centralize ng data at ng mga paraan upang baguhin ito. Ang daloy ng data ay magiging ganito:
Hindi natin tatalakayin dito ang bahagi kung saan ang data ay awtomatikong nagti-trigger ng pag-update ng view, dahil ito ay konektado sa mas advanced na mga konsepto ng Reactive Programming. Magandang follow-up na paksa ito kung nais mong mag-dive nang mas malalim.
✅ Maraming mga library na may iba't ibang paraan sa state management, ang Redux ay isang 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 maaari mong harapin sa malalaking web app at kung paano ito malulutas.
Gawain
Magsisimula tayo sa kaunting refactoring. Palitan ang deklarasyon ng account
:
let account = null;
Ng:
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 ito ay nagbubukas ng daan para sa mga 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 malaking pagbabago, 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 upang 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 kasanayan din na ituring itong immutable, ibig sabihin hindi ito maaaring baguhin. Nangangahulugan din ito na kailangan mong lumikha ng bagong state object kung nais 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 ginagawang mas madali ang pag-debug. Halimbawa, maaari mong i-log ang bawat pagbabago sa state at panatilihin ang kasaysayan 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, magbubunga ito ng exception.
✅ Alam mo ba ang pagkakaiba ng isang shallow at isang 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 ay pinapalitan natin ang isang 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 paraang 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 naka-freeze 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 ay samantalahin natin ang pagkakataon upang ayusin ang isyu ng hindi nalilinis na account data 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 maayos pa rin ang lahat.
Tip: Maaari mong tingnan ang lahat ng pagbabago sa state sa pamamagitan ng pagdaragdag ng
console.log(state)
sa ibaba ngupdateState()
at pagbubukas 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 kritikal 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 ring magpanatili ng ilang data sa client app na tumatakbo sa iyong browser, para sa mas magandang karanasan ng user o upang mapabuti ang loading performance.
Kapag nais 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 sensitibong 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 nais mo itong i-store nang permanente?
Mayroong iba't ibang paraan ng pag-store ng impormasyon sa loob ng isang web app, depende sa nais 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 kailangang i-share ang data 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 na magpanatili 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 tulad nglocalStorage
maliban na ang data na naka-store dito ay nabubura kapag natapos ang session (kapag isinara ang browser).
Tandaan na ang parehong mga API na ito ay nagpapahintulot lamang na mag-store ng strings. Kung nais mong mag-store ng mga kumplikadong object, kailangan mong i-serialize ito sa JSON format gamit ang JSON.stringify()
.
✅ Kung nais mong gumawa ng web app na hindi gumagana sa server, posible ring gumawa ng database sa client gamit ang IndexedDB
API. Ang API na ito ay para sa mga advanced na kaso ng paggamit o kung kailangan mong mag-store ng malaking dami ng data, dahil mas kumplikado itong gamitin.
Gawain
Nais nating manatiling naka-log in ang ating mga user hanggang sa sila ay mag-click nang tahasan sa Logout button, kaya gagamit tayo ng localStorage
upang mag-store ng account data. Una, mag-define tayo ng key na gagamitin natin upang mag-store ng 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 mananatili at palaging up-to-date dahil na-centralize na natin ang lahat ng ating state updates. Dito natin sinisimulang maramdaman ang benepisyo ng lahat ng ating mga naunang refactor 🙂.
Dahil ang data ay naka-save, kailangan din nating tiyakin na ito ay mare-restore kapag ang app ay na-load. Dahil magkakaroon na tayo ng mas maraming initialization code, maaaring magandang ideya na gumawa ng bagong init
function, na kasama rin ang ating dating 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, dahil ngayon ay pinapanatili na natin ang account data. Kung walang data na makita, ang dashboard ang bahalang mag-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 unang 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 pinapanatili 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 i-reload ang account data tuwing ang dashboard ay na-load, 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 sinusuri kung kasalukuyan tayong naka-log in at 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 ina-update ang account data, pagkatapos ay ina-update ang HTML ng dashboard page. Ito ang kailangan nating tawagin kapag ang dashboard route ay na-load. I-update ang route definition gamit ang:
const routes = {
'/login': { templateId: 'login' },
'/dashboard': { templateId: 'dashboard', init: refresh }
};
Subukang i-reload ang dashboard ngayon, dapat nitong ipakita ang na-update na account data.
🚀 Hamon
Ngayon na nire-reload natin ang account data tuwing ang dashboard ay na-load, sa tingin mo ba kailangan pa nating panatilihin ang 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 "Add transaction" dialog
Narito ang isang halimbawa ng resulta matapos makumpleto ang takdang-aralin:
Paunawa:
Ang dokumentong ito ay isinalin gamit ang AI translation service na Co-op Translator. Bagama't sinisikap naming maging tumpak, pakitandaan na ang mga awtomatikong pagsasalin ay maaaring maglaman ng mga pagkakamali o hindi pagkakatugma. Ang orihinal na dokumento sa orihinal nitong 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 maaaring magmula sa paggamit ng pagsasaling ito.