16 KiB
Vytvoření bankovní aplikace, část 4: Koncepty správy stavu
Kvíz před přednáškou
Úvod
Jak webová aplikace roste, stává se stále obtížnější sledovat všechny datové toky. Který kód získává data, která stránka je spotřebovává, kde a kdy je třeba je aktualizovat... snadno se dostanete k chaotickému kódu, který je obtížné udržovat. To platí zejména tehdy, když potřebujete sdílet data mezi různými stránkami vaší aplikace, například uživatelská data. Koncept správy stavu vždy existoval ve všech typech programů, ale jak webové aplikace stále rostou na složitosti, stává se klíčovým bodem, o kterém je třeba během vývoje přemýšlet.
V této poslední části se podíváme na aplikaci, kterou jsme vytvořili, abychom přehodnotili, jak je spravován stav, což umožní podporu obnovení prohlížeče kdykoli a zachování dat mezi uživatelskými relacemi.
Předpoklady
Pro tuto lekci musíte mít dokončenou část získávání dat webové aplikace. Také musíte nainstalovat Node.js a spustit server API lokálně, abyste mohli spravovat data účtu.
Můžete otestovat, zda server běží správně, spuštěním tohoto příkazu v terminálu:
curl http://localhost:5000/api
# -> should return "Bank API v1.0.0" as a result
Přehodnocení správy stavu
V předchozí lekci jsme představili základní koncept stavu v naší aplikaci s globální proměnnou account
, která obsahuje bankovní data aktuálně přihlášeného uživatele. Nicméně naše současná implementace má určité nedostatky. Zkuste obnovit stránku, když jste na dashboardu. Co se stane?
Existují 3 problémy se současným kódem:
- Stav není zachován, protože obnovení prohlížeče vás vrátí na přihlašovací stránku.
- Existuje několik funkcí, které upravují stav. Jak aplikace roste, může být obtížné sledovat změny a snadno zapomenete aktualizovat jednu z nich.
- Stav není vyčištěn, takže když kliknete na Odhlásit se, data účtu tam stále jsou, i když jste na přihlašovací stránce.
Mohli bychom aktualizovat náš kód, abychom tyto problémy řešili jeden po druhém, ale vytvořilo by to více duplicitního kódu a učinilo aplikaci složitější a obtížněji udržovatelnou. Nebo bychom se mohli na pár minut zastavit a přehodnotit naši strategii.
Jaké problémy se zde vlastně snažíme vyřešit?
Správa stavu je o nalezení dobrého přístupu k řešení těchto dvou konkrétních problémů:
- Jak udržet datové toky v aplikaci srozumitelné?
- Jak udržet stavová data vždy synchronizovaná s uživatelským rozhraním (a naopak)?
Jakmile se o tyto problémy postaráte, jakékoli další problémy, které byste mohli mít, mohou být buď již vyřešeny, nebo se staly snadněji řešitelnými. Existuje mnoho možných přístupů k řešení těchto problémů, ale zvolíme běžné řešení, které spočívá v centralizaci dat a způsobů jejich změny. Datové toky by vypadaly takto:
Zde nebudeme pokrývat část, kde data automaticky spouštějí aktualizaci zobrazení, protože je spojena s pokročilejšími koncepty reaktivního programování. Je to dobré téma pro hlubší studium, pokud máte zájem.
✅ Existuje mnoho knihoven s různými přístupy ke správě stavu, Redux je populární volbou. Podívejte se na koncepty a vzory, které používá, protože často poskytují dobrý přehled o potenciálních problémech, kterým můžete čelit ve velkých webových aplikacích, a o tom, jak je lze vyřešit.
Úkol
Začneme malým refaktoringem. Nahraďte deklaraci account
:
let account = null;
Tímto:
let state = {
account: null
};
Myšlenkou je centralizovat všechna data naší aplikace do jediného objektu stavu. Zatím máme ve stavu pouze account
, takže se toho moc nezmění, ale vytváříme cestu pro budoucí rozšíření.
Musíme také aktualizovat funkce, které jej používají. Ve funkcích register()
a login()
nahraďte account = ...
za state.account = ...
;
Na začátek funkce updateDashboard()
přidejte tento řádek:
const account = state.account;
Tento refaktoring sám o sobě nepřinesl mnoho zlepšení, ale myšlenkou bylo položit základy pro další změny.
Sledování změn dat
Nyní, když jsme vytvořili objekt state
pro ukládání našich dat, dalším krokem je centralizace aktualizací. Cílem je usnadnit sledování jakýchkoli změn a kdy k nim dochází.
Aby se zabránilo změnám objektu state
, je také dobré považovat jej za neměnný, což znamená, že jej nelze vůbec upravovat. To také znamená, že musíte vytvořit nový objekt stavu, pokud chcete něco změnit. Tímto způsobem vytváříte ochranu proti potenciálně nežádoucím vedlejším účinkům a otevíráte možnosti pro nové funkce ve vaší aplikaci, jako je implementace undo/redo, a zároveň usnadňujete ladění. Například byste mohli zaznamenávat každou změnu provedenou ve stavu a uchovávat historii změn, abyste pochopili zdroj chyby.
V JavaScriptu můžete použít Object.freeze()
k vytvoření neměnné verze objektu. Pokud se pokusíte provést změny neměnného objektu, bude vyvolána výjimka.
✅ Znáte rozdíl mezi mělkým a hlubokým neměnným objektem? Můžete si o tom přečíst zde.
Úkol
Vytvořme novou funkci updateState()
:
function updateState(property, newData) {
state = Object.freeze({
...state,
[property]: newData
});
}
V této funkci vytváříme nový objekt stavu a kopírujeme data z předchozího stavu pomocí operátoru rozbalení (...
). Poté přepisujeme konkrétní vlastnost objektu stavu novými daty pomocí notace hranatých závorek [property]
pro přiřazení. Nakonec objekt uzamkneme, aby se zabránilo úpravám pomocí Object.freeze()
. Zatím máme ve stavu pouze vlastnost account
, ale s tímto přístupem můžete do stavu přidat tolik vlastností, kolik potřebujete.
Také aktualizujeme inicializaci state
, abychom zajistili, že počáteční stav bude také zmrazen:
let state = Object.freeze({
account: null
});
Poté aktualizujte funkci register
nahrazením přiřazení state.account = result;
za:
updateState('account', result);
Uděláme totéž s funkcí login
, nahrazením state.account = data;
za:
updateState('account', data);
Nyní využijeme příležitosti k vyřešení problému, kdy data účtu nejsou vymazána, když uživatel klikne na Odhlásit se.
Vytvořte novou funkci logout()
:
function logout() {
updateState('account', null);
navigate('/login');
}
V updateDashboard()
nahraďte přesměrování return navigate('/login');
za return logout()
;
Zkuste zaregistrovat nový účet, odhlásit se a znovu se přihlásit, abyste ověřili, že vše stále funguje správně.
Tip: můžete se podívat na všechny změny stavu přidáním
console.log(state)
na konecupdateState()
a otevřením konzole v nástrojích pro vývojáře vašeho prohlížeče.
Zachování stavu
Většina webových aplikací potřebuje uchovávat data, aby mohla správně fungovat. Všechna kritická data jsou obvykle uložena v databázi a přistupuje se k nim prostřednictvím serverového API, například k datům uživatelského účtu v našem případě. Ale někdy je také zajímavé uchovávat některá data na klientské aplikaci, která běží ve vašem prohlížeči, pro lepší uživatelský zážitek nebo pro zlepšení výkonu načítání.
Když chcete uchovávat data ve svém prohlížeči, existuje několik důležitých otázek, které byste si měli položit:
- Jsou data citlivá? Měli byste se vyhnout ukládání jakýchkoli citlivých dat na klienta, jako jsou hesla uživatelů.
- Jak dlouho potřebujete tato data uchovávat? Plánujete přístup k těmto datům pouze pro aktuální relaci, nebo je chcete uchovávat navždy?
Existuje několik způsobů, jak ukládat informace uvnitř webové aplikace, v závislosti na tom, čeho chcete dosáhnout. Například můžete použít URL k uložení vyhledávacího dotazu a učinit jej sdílitelným mezi uživateli. Můžete také použít HTTP cookies, pokud je třeba data sdílet se serverem, například informace o autentizaci.
Další možností je použití jedné z mnoha API prohlížeče pro ukládání dat. Dvě z nich jsou obzvláště zajímavé:
localStorage
: Key/Value store, který umožňuje uchovávat data specifická pro aktuální webovou stránku mezi různými relacemi. Data uložená v něm nikdy nevyprší.sessionStorage
: funguje stejně jakolocalStorage
, kromě toho, že data uložená v něm jsou vymazána, když relace skončí (když se prohlížeč zavře).
Všimněte si, že obě tyto API umožňují ukládat pouze řetězce. Pokud chcete ukládat složité objekty, budete je muset serializovat do formátu JSON pomocí JSON.stringify()
.
✅ Pokud chcete vytvořit webovou aplikaci, která nepracuje se serverem, je také možné vytvořit databázi na klientovi pomocí IndexedDB
API. Toto je vyhrazeno pro pokročilé případy použití nebo pokud potřebujete ukládat významné množství dat, protože je složitější na použití.
Úkol
Chceme, aby naši uživatelé zůstali přihlášeni, dokud explicitně nekliknou na tlačítko Odhlásit se, takže použijeme localStorage
k ukládání dat účtu. Nejprve definujeme klíč, který použijeme k ukládání našich dat.
const storageKey = 'savedAccount';
Poté přidejte tento řádek na konec funkce updateState()
:
localStorage.setItem(storageKey, JSON.stringify(state.account));
Tímto způsobem budou data uživatelského účtu uchována a vždy aktuální, protože jsme dříve centralizovali všechny naše aktualizace stavu. Zde začínáme těžit ze všech našich předchozích refaktorů 🙂.
Protože jsou data uložena, musíme se také postarat o jejich obnovení, když je aplikace načtena. Vzhledem k tomu, že začneme mít více inicializačního kódu, může být dobrý nápad vytvořit novou funkci init
, která také zahrnuje náš předchozí kód na konci 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();
Zde získáváme uložená data a pokud nějaká existují, aktualizujeme stav odpovídajícím způsobem. Je důležité to udělat před aktualizací trasy, protože během aktualizace stránky může být kód závislý na stavu.
Můžeme také udělat stránku Dashboard výchozí stránkou naší aplikace, protože nyní uchováváme data účtu. Pokud žádná data nejsou nalezena, dashboard se postará o přesměrování na stránku Login. V updateRoute()
nahraďte výchozí return navigate('/login');
za return navigate('/dashboard');
.
Nyní se přihlaste do aplikace a zkuste obnovit stránku. Měli byste zůstat na dashboardu. S touto aktualizací jsme se postarali o všechny naše počáteční problémy...
Aktualizace dat
...Ale možná jsme také vytvořili nový problém. Ups!
Přejděte na dashboard pomocí účtu test
, poté spusťte tento příkaz v terminálu, abyste vytvořili novou transakci:
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
Zkuste nyní obnovit stránku dashboardu v prohlížeči. Co se stane? Vidíte novou transakci?
Stav je uchováván neomezeně díky localStorage
, ale to také znamená, že se nikdy neaktualizuje, dokud se z aplikace neodhlásíte a znovu nepřihlásíte!
Jednou z možných strategií, jak to opravit, je znovu načíst data účtu pokaždé, když je dashboard načten, aby se zabránilo zastaralým datům.
Úkol
Vytvořte novou funkci 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);
}
Tato metoda kontroluje, zda jsme aktuálně přihlášeni, a poté znovu načte data účtu ze serveru.
Vytvořte další funkci nazvanou refresh
:
async function refresh() {
await updateAccountData();
updateDashboard();
}
Tato funkce aktualizuje data účtu a poté se postará o aktualizaci HTML stránky dashboardu. To je to, co potřebujeme zavolat, když je načtena trasa dashboardu. Aktualizujte definici trasy:
const routes = {
'/login': { templateId: 'login' },
'/dashboard': { templateId: 'dashboard', init: refresh }
};
Zkuste nyní obnovit dashboard, měl by zobrazit aktualizovaná data účtu.
🚀 Výzva
Nyní, když znovu načítáme data účtu pokaždé, když je dashboard načten, myslíte si, že stále potřebujeme uchovávat všechna data účtu?
Zkuste společně upravit, co je ukládáno a načítáno z localStorage
, aby zahrnovalo pouze to, co je absolutně nezbytné pro fungování aplikace.
Kvíz po přednášce
Zadání
Implementace dialogu "Přidat transakci"
Zde je ukázkový výsledek po dokončení úkolu:
Prohlášení:
Tento dokument byl přeložen pomocí služby pro automatický překlad Co-op Translator. I když se snažíme o co největší přesnost, mějte prosím na paměti, že automatické překlady mohou obsahovat chyby nebo nepřesnosti. Původní dokument v jeho původním jazyce by měl být považován za závazný zdroj. Pro důležité informace doporučujeme profesionální lidský překlad. Neodpovídáme za žádná nedorozumění nebo nesprávné výklady vyplývající z použití tohoto překladu.