25 KiB
Създаване на Банкова Приложение Част 4: Концепции за Управление на Състоянието
Предварителен Тест
Въведение
С разрастването на уеб приложението става предизвикателство да се следят всички потоци от данни. Кой код получава данните, коя страница ги използва, къде и кога трябва да бъдат актуализирани... лесно е да се стигне до объркан код, който е труден за поддръжка. Това е особено вярно, когато трябва да споделяте данни между различни страници на приложението, например данни за потребителя. Концепцията за управление на състоянието винаги е съществувала във всякакви програми, но с нарастващата сложност на уеб приложенията тя вече е ключова точка за обмисляне по време на разработката.
В тази последна част ще разгледаме приложението, което създадохме, за да преосмислим как се управлява състоянието, като позволим поддръжка на обновяване на браузъра във всеки момент и запазване на данните между потребителските сесии.
Предпоставки
Трябва да сте завършили частта за извличане на данни от уеб приложението за този урок. Също така трябва да инсталирате Node.js и да стартирате API сървъра локално, за да можете да управлявате данните за акаунта.
Можете да тествате дали сървърът работи правилно, като изпълните тази команда в терминала:
curl http://localhost:5000/api
# -> should return "Bank API v1.0.0" as a result
Преосмисляне на управлението на състоянието
В предишния урок въведохме основна концепция за състояние в нашето приложение с глобалната променлива account
, която съдържа банковите данни за текущо влезлия потребител. Въпреки това, текущата ни имплементация има някои недостатъци. Опитайте да обновите страницата, когато сте на таблото. Какво се случва?
Има три проблема с текущия код:
- Състоянието не се запазва, тъй като обновяването на браузъра ви връща обратно на страницата за вход.
- Има множество функции, които модифицират състоянието. С разрастването на приложението това може да затрудни проследяването на промените и лесно да се забрави актуализирането на някоя.
- Състоянието не се изчиства, така че когато кликнете върху Изход, данните за акаунта все още са там, въпреки че сте на страницата за вход.
Можем да актуализираме кода си, за да се справим с тези проблеми един по един, но това би създало повече дублиране на код и би направило приложението по-сложно и трудно за поддръжка. Или можем да спрем за няколко минути и да преосмислим стратегията си.
Какви проблеми всъщност се опитваме да решим тук?
Управлението на състоянието е свързано с намирането на добър подход за решаване на тези два конкретни проблема:
- Как да направим потоците от данни в приложението разбираеми?
- Как да поддържаме данните за състоянието винаги синхронизирани с потребителския интерфейс (и обратно)?
След като се погрижите за тези проблеми, всички други проблеми, които може да имате, или ще бъдат вече решени, или ще станат по-лесни за решаване. Има много възможни подходи за решаване на тези проблеми, но ние ще използваме общо решение, което се състои в централизиране на данните и начините за тяхната промяна. Потокът от данни би изглеждал така:
Тук няма да разглеждаме частта, където данните автоматично задействат актуализацията на изгледа, тъй като тя е свързана с по-напреднали концепции на Реактивното програмиране. Това е добра тема за последващо задълбочено изучаване.
✅ Има много библиотеки с различни подходи за управление на състоянието, като Redux е популярна опция. Разгледайте концепциите и моделите, които се използват, тъй като често е добър начин да научите какви потенциални проблеми може да срещнете в големи уеб приложения и как те могат да бъдат решени.
Задача
Ще започнем с малко рефакториране. Заменете декларацията на account
:
let account = null;
С:
let state = {
account: null
};
Идеята е да централизираме всички данни на приложението в един обект за състояние. Засега имаме само account
в състоянието, така че това не променя много, но създава път за бъдещи подобрения.
Също така трябва да актуализираме функциите, които го използват. Във функциите register()
и login()
заменете account = ...
с state.account = ...
;
В началото на функцията updateDashboard()
добавете този ред:
const account = state.account;
Това рефакториране само по себе си не донесе много подобрения, но идеята беше да се положат основите за следващите промени.
Проследяване на промените в данните
Сега, когато сме поставили обекта state
, за да съхраняваме данните си, следващата стъпка е да централизиране актуализациите. Целта е да се улесни проследяването на всякакви промени и кога те се случват.
За да избегнем промени в обекта state
, също е добра практика да го считаме за неизменяем, което означава, че той изобщо не може да бъде модифициран. Това също означава, че трябва да създадете нов обект за състояние, ако искате да промените нещо в него. Като правите това, изграждате защита срещу потенциално нежелани странични ефекти и отваряте възможности за нови функции в приложението си, като например внедряване на undo/redo, като същевременно улеснявате дебъгването. Например, можете да регистрирате всяка промяна, направена в състоянието, и да запазите история на промените, за да разберете източника на грешка.
В JavaScript можете да използвате Object.freeze()
, за да създадете неизменяема версия на обект. Ако се опитате да направите промени в неизменяем обект, ще бъде хвърлено изключение.
✅ Знаете ли разликата между повърхностно и дълбоко неизменяем обект? Можете да прочетете за това тук.
Задача
Нека създадем нова функция updateState()
:
function updateState(property, newData) {
state = Object.freeze({
...state,
[property]: newData
});
}
В тази функция създаваме нов обект за състояние и копираме данни от предишното състояние, използвайки оператора за разпръскване (...
). След това презаписваме конкретно свойство на обекта за състояние с новите данни, използвайки нотацията с квадратни скоби [property]
за присвояване. Накрая заключваме обекта, за да предотвратим модификации, използвайки Object.freeze()
. Засега имаме само свойството account
, съхранено в състоянието, но с този подход можете да добавите толкова свойства, колкото са ви необходими.
Също така ще актуализираме инициализацията на state
, за да се уверим, че началното състояние също е заключено:
let state = Object.freeze({
account: null
});
След това актуализирайте функцията register
, като замените присвояването state.account = result;
с:
updateState('account', result);
Направете същото с функцията login
, като замените state.account = data;
с:
updateState('account', data);
Сега ще използваме възможността да поправим проблема с данните за акаунта, които не се изчистват, когато потребителят кликне върху Изход.
Създайте нова функция logout()
:
function logout() {
updateState('account', null);
navigate('/login');
}
В updateDashboard()
заменете пренасочването return navigate('/login');
с return logout();
Опитайте да регистрирате нов акаунт, да излезете и да влезете отново, за да проверите дали всичко все още работи правилно.
Съвет: можете да разгледате всички промени в състоянието, като добавите
console.log(state)
в края наupdateState()
и отворите конзолата в инструментите за разработка на браузъра.
Запазване на състоянието
Повечето уеб приложения трябва да запазват данни, за да могат да работят правилно. Всички критични данни обикновено се съхраняват в база данни и се достъпват чрез API сървър, като например данните за потребителския акаунт в нашия случай. Но понякога е интересно да се запазят някои данни в клиентското приложение, което работи в браузъра, за по-добро потребителско изживяване или за подобряване на производителността при зареждане.
Когато искате да запазите данни в браузъра, има няколко важни въпроса, които трябва да си зададете:
- Данните чувствителни ли са? Трябва да избягвате съхраняването на чувствителни данни в клиента, като например пароли на потребители.
- За колко време трябва да запазите тези данни? Планирате ли да достъпите тези данни само за текущата сесия или искате те да бъдат съхранени завинаги?
Има множество начини за съхраняване на информация в уеб приложение, в зависимост от това, което искате да постигнете. Например, можете да използвате URL адресите, за да съхранявате заявка за търсене и да я направите споделима между потребителите. Можете също така да използвате HTTP бисквитки, ако данните трябва да бъдат споделени със сървъра, като информация за автентикация.
Друга опция е да използвате една от многото API на браузъра за съхраняване на данни. Две от тях са особено интересни:
localStorage
: Key/Value хранилище, което позволява запазване на данни, специфични за текущия уеб сайт, между различни сесии. Данните, запазени в него, никога не изтичат.sessionStorage
: работи по същия начин катоlocalStorage
, с изключение на това, че данните, съхранени в него, се изчистват, когато сесията приключи (когато браузърът се затвори).
Имайте предвид, че и двата API позволяват съхраняване само на стрингове. Ако искате да съхранявате сложни обекти, ще трябва да ги сериализирате във формат JSON, използвайки JSON.stringify()
.
✅ Ако искате да създадете уеб приложение, което не работи със сървър, е възможно да създадете база данни на клиента, използвайки API IndexedDB
. Това е запазено за напреднали случаи на употреба или ако трябва да съхранявате значително количество данни, тъй като е по-сложно за използване.
Задача
Искаме нашите потребители да останат влезли, докато не кликнат изрично върху бутона Изход, така че ще използваме localStorage
, за да съхраняваме данните за акаунта. Първо, нека дефинираме ключ, който ще използваме за съхраняване на нашите данни.
const storageKey = 'savedAccount';
След това добавете този ред в края на функцията updateState()
:
localStorage.setItem(storageKey, JSON.stringify(state.account));
С това данните за потребителския акаунт ще бъдат запазени и винаги актуални, както централизирахме преди всички актуализации на състоянието. Тук започваме да се възползваме от всички предишни рефакторирания 🙂.
Тъй като данните се запазват, трябва също да се погрижим за тяхното възстановяване, когато приложението се зареди. Тъй като ще започнем да имаме повече код за инициализация, може би е добра идея да създадем нова функция init
, която също включва предишния код в края на 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();
Тук извличаме запазените данни и ако има такива, актуализираме състоянието съответно. Важно е да направим това преди актуализирането на маршрута, тъй като може да има код, който разчита на състоянието по време на актуализацията на страницата.
Също така можем да направим страницата Табло основната страница на нашето приложение, тъй като сега запазваме данните за акаунта. Ако не се намерят данни, таблото се грижи за пренасочването към страницата Вход така или иначе. В updateRoute()
заменете резервния return navigate('/login');
с return navigate('/dashboard');
.
Сега влезте в приложението и опитайте да обновите страницата. Трябва да останете на таблото. С тази актуализация сме се погрижили за всички първоначални проблеми...
Обновяване на данните
...Но може би сме създали нов проблем. Опа!
Отидете на таблото, използвайки акаунта test
, след това изпълнете тази команда в терминала, за да създадете нова транзакция:
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
:
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
:
async function refresh() {
await updateAccountData();
updateDashboard();
}
Тази функция актуализира данните за акаунта, след това се грижи за актуализирането на HTML на страницата на таблото. Това е, което трябва да извикаме, когато маршрутът на таблото се зареди. Актуализирайте дефиницията на маршрута с:
const routes = {
'/login': { templateId: 'login' },
'/dashboard': { templateId: 'dashboard', init: refresh }
};
Опитайте да обновите таблото сега, то трябва да показва актуализираните данни за акаунта Реализиране на диалог "Добавяне на транзакция"
Ето примерен резултат след завършване на задачата:
Отказ от отговорност:
Този документ е преведен с помощта на AI услуга за превод Co-op Translator. Въпреки че се стремим към точност, моля, имайте предвид, че автоматичните преводи може да съдържат грешки или неточности. Оригиналният документ на неговия изходен език трябва да се счита за авторитетен източник. За критична информация се препоръчва професионален превод от човек. Ние не носим отговорност за каквито и да е недоразумения или погрешни интерпретации, произтичащи от използването на този превод.