|
3 weeks ago | |
---|---|---|
.. | ||
README.md | 3 weeks ago | |
assignment.md | 4 weeks ago |
README.md
Bygg en Bankapp Del 4: Koncept för Tillståndshantering
Förkunskapstest
Introduktion
När en webbapplikation växer blir det en utmaning att hålla reda på alla dataflöden. Vilken kod hämtar datan, vilken sida använder den, var och när behöver den uppdateras... det är lätt att hamna med rörig kod som är svår att underhålla. Detta är särskilt sant när du behöver dela data mellan olika sidor i din app, till exempel användardata. Konceptet tillståndshantering har alltid funnits i alla typer av program, men eftersom webbappar blir alltmer komplexa är det nu en nyckelfråga att tänka på under utvecklingen.
I denna sista del kommer vi att granska appen vi byggt för att omvärdera hur tillstånd hanteras, vilket möjliggör stöd för att uppdatera webbläsaren när som helst och bevara data mellan användarsessioner.
Förutsättningar
Du behöver ha slutfört datahämtning-delen av webbappen för denna lektion. Du behöver också installera Node.js och köra server-API:t lokalt så att du kan hantera kontodata.
Du kan testa att servern fungerar korrekt genom att köra detta kommando i en terminal:
curl http://localhost:5000/api
# -> should return "Bank API v1.0.0" as a result
Omvärdera tillståndshantering
I föregående lektion introducerade vi ett grundläggande koncept för tillstånd i vår app med den globala variabeln account
som innehåller bankdatan för den inloggade användaren. Men vår nuvarande implementation har vissa brister. Försök att uppdatera sidan när du är på instrumentpanelen. Vad händer?
Det finns tre problem med den nuvarande koden:
- Tillståndet sparas inte, eftersom en uppdatering av webbläsaren tar dig tillbaka till inloggningssidan.
- Det finns flera funktioner som ändrar tillståndet. När appen växer kan det bli svårt att hålla reda på ändringarna och lätt att glömma att uppdatera något.
- Tillståndet rensas inte, så när du klickar på Logga ut finns kontodatan fortfarande kvar även om du är på inloggningssidan.
Vi skulle kunna uppdatera vår kod för att lösa dessa problem ett i taget, men det skulle skapa mer kodupprepning och göra appen mer komplex och svår att underhålla. Eller så kan vi pausa i några minuter och omvärdera vår strategi.
Vilka problem försöker vi egentligen lösa här?
Tillståndshantering handlar om att hitta ett bra sätt att lösa dessa två specifika problem:
- Hur håller vi dataflödena i en app begripliga?
- Hur håller vi tillståndsdata alltid synkroniserade med användargränssnittet (och vice versa)?
När du har tagit hand om dessa kan andra problem antingen redan vara lösta eller bli enklare att lösa. Det finns många möjliga tillvägagångssätt för att lösa dessa problem, men vi kommer att använda en vanlig lösning som består av att centralisera datan och sätten att ändra den. Dataflödena skulle se ut så här:
Vi kommer inte att täcka delen där data automatiskt uppdaterar vyn, eftersom det är kopplat till mer avancerade koncept inom Reaktiv Programmering. Det är ett bra ämne att fördjupa sig i om du vill gå djupare.
✅ Det finns många bibliotek där ute med olika tillvägagångssätt för tillståndshantering, Redux är ett populärt alternativ. Ta en titt på koncepten och mönstren som används, eftersom det ofta är ett bra sätt att lära sig vilka potentiella problem du kan stöta på i stora webbappar och hur de kan lösas.
Uppgift
Vi börjar med lite omstrukturering. Ersätt deklarationen av account
:
let account = null;
Med:
let state = {
account: null
};
Tanken är att centralisera all vår appdata i ett enda tillståndsobjekt. Vi har bara account
för tillfället i tillståndet, så det förändrar inte mycket, men det skapar en väg för framtida utveckling.
Vi måste också uppdatera funktionerna som använder det. I funktionerna register()
och login()
, ersätt account = ...
med state.account = ...
;
Lägg till denna rad högst upp i funktionen updateDashboard()
:
const account = state.account;
Denna omstrukturering i sig gav inte så stora förbättringar, men tanken var att lägga grunden för de kommande ändringarna.
Spåra dataändringar
Nu när vi har infört objektet state
för att lagra vår data är nästa steg att centralisera uppdateringarna. Målet är att göra det enklare att hålla reda på alla ändringar och när de sker.
För att undvika att ändringar görs direkt i objektet state
är det också en bra praxis att betrakta det som oföränderligt, vilket innebär att det inte kan ändras alls. Det innebär också att du måste skapa ett nytt tillståndsobjekt om du vill ändra något i det. Genom att göra detta bygger du ett skydd mot potentiellt oönskade bieffekter och öppnar upp möjligheter för nya funktioner i din app, som att implementera ångra/gör om, samtidigt som det blir enklare att felsöka. Till exempel kan du logga varje ändring som görs i tillståndet och hålla en historik över ändringarna för att förstå källan till ett fel.
I JavaScript kan du använda Object.freeze()
för att skapa en oföränderlig version av ett objekt. Om du försöker göra ändringar i ett oföränderligt objekt kommer ett undantag att kastas.
✅ Vet du skillnaden mellan ett ytligt och ett djupt oföränderligt objekt? Du kan läsa om det här.
Uppgift
Låt oss skapa en ny funktion updateState()
:
function updateState(property, newData) {
state = Object.freeze({
...state,
[property]: newData
});
}
I denna funktion skapar vi ett nytt tillståndsobjekt och kopierar data från det tidigare tillståndet med hjälp av spridningsoperatorn (...
). Sedan skriver vi över en specifik egenskap i tillståndsobjektet med den nya datan med hjälp av haknotationen [property]
för tilldelning. Slutligen låser vi objektet för att förhindra ändringar med Object.freeze()
. Vi har bara egenskapen account
lagrad i tillståndet för tillfället, men med detta tillvägagångssätt kan du lägga till så många egenskaper som du behöver i tillståndet.
Vi uppdaterar också initialiseringen av state
för att säkerställa att det initiala tillståndet också är fryst:
let state = Object.freeze({
account: null
});
Efter det, uppdatera funktionen register
genom att ersätta tilldelningen state.account = result;
med:
updateState('account', result);
Gör samma sak med funktionen login
, ersätt state.account = data;
med:
updateState('account', data);
Vi passar nu på att lösa problemet med att kontodatan inte rensas när användaren klickar på Logga ut.
Skapa en ny funktion logout()
:
function logout() {
updateState('account', null);
navigate('/login');
}
I updateDashboard()
, ersätt omdirigeringen return navigate('/login');
med return logout();
.
Försök att registrera ett nytt konto, logga ut och logga in igen för att kontrollera att allt fortfarande fungerar korrekt.
Tips: Du kan titta på alla tillståndsändringar genom att lägga till
console.log(state)
längst ner iupdateState()
och öppna konsolen i din webbläsares utvecklingsverktyg.
Bevara tillståndet
De flesta webbappar behöver bevara data för att fungera korrekt. All kritisk data lagras vanligtvis i en databas och nås via ett server-API, som användarkontodata i vårt fall. Men ibland är det också intressant att bevara viss data i klientappen som körs i din webbläsare, för en bättre användarupplevelse eller för att förbättra laddningsprestandan.
När du vill bevara data i din webbläsare finns det några viktiga frågor du bör ställa dig:
- Är datan känslig? Du bör undvika att lagra känslig data på klienten, som användarlösenord.
- Hur länge behöver du behålla denna data? Planerar du att använda denna data endast under den aktuella sessionen eller vill du att den ska lagras för alltid?
Det finns flera sätt att lagra information i en webbapp, beroende på vad du vill uppnå. Till exempel kan du använda URL:er för att lagra en sökfråga och göra den delbar mellan användare. Du kan också använda HTTP-cookies om datan behöver delas med servern, som autentiseringsinformation.
Ett annat alternativ är att använda en av de många webbläsar-API:erna för att lagra data. Två av dem är särskilt intressanta:
localStorage
: en Nyckel/Värde-lagring som gör det möjligt att bevara data specifik för den aktuella webbplatsen mellan olika sessioner. Datan som sparas i den upphör aldrig att gälla.sessionStorage
: denna fungerar på samma sätt somlocalStorage
förutom att datan som lagras i den rensas när sessionen avslutas (när webbläsaren stängs).
Observera att båda dessa API:er endast tillåter lagring av strängar. Om du vill lagra komplexa objekt måste du serialisera dem till JSON-formatet med JSON.stringify()
.
✅ Om du vill skapa en webbapp som inte fungerar med en server är det också möjligt att skapa en databas på klienten med hjälp av IndexedDB
API. Detta är reserverat för avancerade användningsfall eller om du behöver lagra en betydande mängd data, eftersom det är mer komplext att använda.
Uppgift
Vi vill att våra användare ska förbli inloggade tills de uttryckligen klickar på knappen Logga ut, så vi kommer att använda localStorage
för att lagra kontodatan. Först definierar vi en nyckel som vi kommer att använda för att lagra vår data.
const storageKey = 'savedAccount';
Lägg sedan till denna rad i slutet av funktionen updateState()
:
localStorage.setItem(storageKey, JSON.stringify(state.account));
Med detta kommer användarkontodatan att bevaras och alltid vara uppdaterad eftersom vi tidigare centraliserade alla våra tillståndsuppdateringar. Det är här vi börjar dra nytta av alla våra tidigare omstruktureringar 🙂.
Eftersom datan sparas måste vi också ta hand om att återställa den när appen laddas. Eftersom vi börjar få mer initialiseringskod kan det vara en bra idé att skapa en ny funktion init
, som också inkluderar vår tidigare kod längst ner i 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();
Här hämtar vi den sparade datan, och om det finns någon uppdaterar vi tillståndet därefter. Det är viktigt att göra detta innan vi uppdaterar routen, eftersom det kan finnas kod som förlitar sig på tillståndet under siduppdateringen.
Vi kan också göra Instrumentpanelen till vår applikations standardsida, eftersom vi nu bevarar kontodatan. Om ingen data hittas tar instrumentpanelen hand om att omdirigera till Inloggningssidan ändå. I updateRoute()
, ersätt fallbacken return navigate('/login');
med return navigate('/dashboard');
.
Logga nu in i appen och försök att uppdatera sidan. Du bör stanna kvar på instrumentpanelen. Med den uppdateringen har vi tagit hand om alla våra ursprungliga problem...
Uppdatera datan
...Men vi kan också ha skapat ett nytt problem. Oops!
Gå till instrumentpanelen med kontot test
, kör sedan detta kommando i en terminal för att skapa en ny transaktion:
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
Försök att uppdatera instrumentpanelen i webbläsaren nu. Vad händer? Ser du den nya transaktionen?
Tillståndet bevaras på obestämd tid tack vare localStorage
, men det betyder också att det aldrig uppdateras förrän du loggar ut ur appen och loggar in igen!
En möjlig strategi för att lösa detta är att ladda om kontodatan varje gång instrumentpanelen laddas, för att undvika föråldrad data.
Uppgift
Skapa en ny funktion 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);
}
Denna metod kontrollerar att vi för närvarande är inloggade och laddar sedan om kontodatan från servern.
Skapa en annan funktion som heter refresh
:
async function refresh() {
await updateAccountData();
updateDashboard();
}
Denna funktion uppdaterar kontodatan och tar sedan hand om att uppdatera HTML:en på instrumentpanelsidan. Det är vad vi behöver kalla på när routen för instrumentpanelen laddas. Uppdatera routedefinitionen med:
const routes = {
'/login': { templateId: 'login' },
'/dashboard': { templateId: 'dashboard', init: refresh }
};
Försök att ladda om instrumentpanelen nu, den bör visa den uppdaterade kontodatan.
🚀 Utmaning
Nu när vi laddar om kontodatan varje gång instrumentpanelen laddas, tror du att vi fortfarande behöver bevara all kontodata?
Försök att arbeta tillsammans för att ändra vad som sparas och laddas från localStorage
så att det endast inkluderar det som är absolut nödvändigt för att appen ska fungera.
Efterföreläsningstest
Uppgift
Implementera dialogrutan "Lägg till transaktion"
Här är ett exempelresultat efter att uppgiften har slutförts:
Ansvarsfriskrivning:
Detta dokument har översatts med hjälp av AI-översättningstjänsten Co-op Translator. Även om vi strävar efter noggrannhet, bör du vara medveten om att automatiserade översättningar kan innehålla fel eller inexaktheter. Det ursprungliga dokumentet på dess ursprungliga språk bör betraktas som den auktoritativa källan. För kritisk information rekommenderas professionell mänsklig översättning. Vi ansvarar inte för eventuella missförstånd eller feltolkningar som uppstår vid användning av denna översättning.