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, також є гарною практикою вважати його незмінним, тобто таким, що не може бути змінений взагалі. Це також означає, що вам потрібно створити новий об'єкт стану, якщо ви хочете щось у ньому змінити. Таким чином, ви захищаєтеся від потенційно небажаних побічних ефектів і відкриваєте можливості для нових функцій у вашому додатку, таких як реалізація скасування/повтору, а також спрощуєте налагодження. Наприклад, ви могли б реєструвати кожну зміну стану і зберігати історію змін, щоб зрозуміти джерело помилки.
У 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 cookies, якщо дані потрібно передавати на сервер, наприклад, інформацію для автентифікації.
Інший варіант — використовувати один із багатьох API браузера для зберігання даних. Два з них є особливо цікавими:
localStorage: Сховище ключ/значення, яке дозволяє зберігати дані, специфічні для поточного веб-сайту, між різними сеансами. Збережені дані ніколи не закінчуються.sessionStorage: працює так само, якlocalStorage, за винятком того, що дані, збережені в ньому, очищаються після завершення сеансу (коли браузер закривається).
Зверніть увагу, що обидва ці API дозволяють зберігати лише рядки. Якщо ви хочете зберігати складні об'єкти, вам потрібно буде серіалізувати їх у формат JSON за допомогою JSON.stringify().
✅ Якщо ви хочете створити веб-додаток, який не працює з сервером, також можливо створити базу даних на клієнті, використовуючи IndexedDB API. Цей варіант зарезервований для складних випадків використання або якщо вам потрібно зберігати значну кількість даних, оскільки він складніший у використанні.
Завдання
Ми хочемо, щоб наші користувачі залишалися в системі, поки вони явно не натиснуть кнопку Вийти, тому ми будемо використовувати 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();
Тут ми отримуємо збережені дані, і якщо вони є, оновлюємо стан відповідно. Важливо зробити це до оновлення маршруту, оскільки під час оновлення сторінки може бути код, який залежить від стану.
Ми також можемо зробити сторінку Dashboard сторінкою за замовчуванням нашого додатка, оскільки тепер ми зберігаємо дані облікового запису. Якщо дані не знайдені, інформаційна панель все одно перенаправляє на сторінку Login. У 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 }
};
Тепер спробуйте оновити інформаційну панель, вона повинна відображати оновлені дані облікового запису.
🚀 Виклик
Тепер, коли ми перезавантажуємо дані облікового запису щоразу, коли завантажується інформаційна панель, як ви думаєте, чи потрібно нам все ще з Тест після лекції
Завдання
Реалізувати діалогове вікно "Додати транзакцію"
Ось приклад результату після виконання завдання:
Відмова від відповідальності:
Цей документ було перекладено за допомогою сервісу автоматичного перекладу Co-op Translator. Хоча ми прагнемо до точності, будь ласка, зверніть увагу, що автоматичні переклади можуть містити помилки або неточності. Оригінальний документ на його рідній мові слід вважати авторитетним джерелом. Для критичної інформації рекомендується професійний людський переклад. Ми не несемо відповідальності за будь-які непорозуміння або неправильні тлумачення, що виникають внаслідок використання цього перекладу.

