You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Web-Dev-For-Beginners/translations/da/7-bank-project/4-state-management/README.md

26 KiB

Byg en bankapp del 4: Koncepter inden for tilstandshåndtering

Quiz før lektionen

Quiz før lektionen

Introduktion

Tilstandshåndtering er som navigationssystemet på Voyager-rumfartøjet når alt fungerer glat, bemærker du knap nok, at det er der. Men når tingene går galt, kan det være forskellen mellem at nå interstellart rum og at drive fortabt i det kosmiske tomrum. Inden for webudvikling repræsenterer tilstand alt, hvad din applikation skal huske: brugerens loginstatus, formulardata, navigationshistorik og midlertidige grænsefladetilstande.

Efterhånden som din bankapp er udviklet fra en simpel loginformular til en mere sofistikeret applikation, har du sandsynligvis stødt på nogle almindelige udfordringer. Opdater siden, og brugerne bliver uventet logget ud. Luk browseren, og al fremgang forsvinder. Fejlsøg et problem, og du leder gennem flere funktioner, der alle ændrer de samme data på forskellige måder.

Dette er ikke tegn på dårlig kodning det er de naturlige vækstproblemer, der opstår, når applikationer når en vis kompleksitet. Hver udvikler står over for disse udfordringer, når deres apps går fra "proof of concept" til "produktionsklar."

I denne lektion implementerer vi et centraliseret tilstandshåndteringssystem, der forvandler din bankapp til en pålidelig, professionel applikation. Du lærer at håndtere dataflow forudsigeligt, bevare brugersessioner korrekt og skabe den glatte brugeroplevelse, som moderne webapplikationer kræver.

Forudsætninger

Før du dykker ned i tilstandshåndteringskoncepter, skal du have din udviklingsmiljø korrekt opsat og din bankapps fundament på plads. Denne lektion bygger direkte på koncepterne og koden fra de tidligere dele af denne serie.

Sørg for, at du har følgende komponenter klar, før du fortsætter:

Påkrævet opsætning:

  • Fuldfør datahentningslektionen - din app skal kunne indlæse og vise kontodata korrekt
  • Installer Node.js på dit system for at køre backend-API'en
  • Start server-API'en lokalt for at håndtere kontodataoperationer

Test af dit miljø:

Bekræft, at din API-server kører korrekt ved at udføre denne kommando i en terminal:

curl http://localhost:5000/api
# -> should return "Bank API v1.0.0" as a result

Hvad denne kommando gør:

  • Sender en GET-anmodning til din lokale API-server
  • Tester forbindelsen og bekræfter, at serveren svarer
  • Returnerer API-versionens oplysninger, hvis alt fungerer korrekt

Diagnosticering af de nuværende tilstandsproblemer

Som Sherlock Holmes, der undersøger en gerningssted, skal vi forstå præcis, hvad der sker i vores nuværende implementering, før vi kan løse mysteriet om forsvindende brugersessioner.

Lad os udføre et simpelt eksperiment, der afslører de underliggende udfordringer med tilstandshåndtering:

🧪 Prøv denne diagnostiske test:

  1. Log ind på din bankapp og naviger til dashboardet
  2. Opdater browserens side
  3. Observer, hvad der sker med din loginstatus

Hvis du bliver omdirigeret tilbage til login-skærmen, har du opdaget det klassiske problem med tilstandspersistens. Denne adfærd opstår, fordi vores nuværende implementering gemmer brugerdata i JavaScript-variabler, der nulstilles ved hver sideindlæsning.

Problemer med den nuværende implementering:

Den simple account-variabel fra vores tidligere lektion skaber tre væsentlige problemer, der påvirker både brugeroplevelse og kodevedligeholdelse:

Problem Teknisk årsag Brugerpåvirkning
Sessionstab Sideopdatering rydder JavaScript-variabler Brugere skal ofte logge ind igen
Spredte opdateringer Flere funktioner ændrer tilstand direkte Fejlsøgning bliver mere og mere vanskelig
Ufuldstændig oprydning Logout rydder ikke alle tilstandsreferencer Potentielle sikkerheds- og privatlivsproblemer

Den arkitektoniske udfordring:

Ligesom Titanics opdelte design, der virkede robust, indtil flere rum blev oversvømmet samtidig, vil det ikke løse de underliggende arkitektoniske problemer at rette disse problemer individuelt. Vi har brug for en omfattende løsning til tilstandshåndtering.

💡 Hvad prøver vi egentlig at opnå her?

Tilstandshåndtering handler i virkeligheden om at løse to grundlæggende gåder:

  1. Hvor er mine data?: At holde styr på, hvilke oplysninger vi har, og hvor de kommer fra
  2. Er alle på samme side?: At sikre, at det, brugerne ser, stemmer overens med, hvad der faktisk sker

Vores handlingsplan:

I stedet for at jagte vores egen hale, vil vi oprette et centraliseret tilstandshåndteringssystem. Tænk på det som at have én virkelig organiseret person, der har styr på alle de vigtige ting:

Skema, der viser dataflowet mellem HTML, brugerhandlinger og tilstand

Forståelse af dette dataflow:

  • Centraliserer al applikationstilstand ét sted
  • Dirigerer alle tilstandsændringer gennem kontrollerede funktioner
  • Sikrer, at brugergrænsefladen forbliver synkroniseret med den aktuelle tilstand
  • Giver et klart, forudsigeligt mønster for datastyring

💡 Professionel indsigt: Denne lektion fokuserer på grundlæggende koncepter. For komplekse applikationer tilbyder biblioteker som Redux mere avancerede funktioner til tilstandshåndtering. At forstå disse kerneprincipper vil hjælpe dig med at mestre ethvert tilstandshåndteringsbibliotek.

⚠️ Avanceret emne: Vi dækker ikke automatiske UI-opdateringer, der udløses af tilstandsændringer, da dette involverer Reaktiv programmering-koncepter. Overvej dette som et fremragende næste skridt i din læringsrejse!

Opgave: Centraliser tilstandsstruktur

Lad os begynde at transformere vores spredte tilstandshåndtering til et centraliseret system. Dette første skridt etablerer fundamentet for alle de forbedringer, der følger.

Trin 1: Opret et centraliseret tilstandsobjekt

Erstat den simple account-deklaration:

let account = null;

Med et struktureret tilstandsobjekt:

let state = {
  account: null
};

Hvorfor denne ændring er vigtig:

  • Centraliserer alle applikationsdata ét sted
  • Forbereder strukturen til at tilføje flere tilstandsegenskaber senere
  • Skaber en klar grænse mellem tilstand og andre variabler
  • Etablerer et mønster, der skalerer, efterhånden som din app vokser

Trin 2: Opdater tilstandsadgangsmønstre

Opdater dine funktioner til at bruge den nye tilstandsstruktur:

I register() og login() funktionerne, erstat:

account = ...

Med:

state.account = ...

I updateDashboard() funktionen, tilføj denne linje øverst:

const account = state.account;

Hvad disse opdateringer opnår:

  • Bevarer eksisterende funktionalitet, mens strukturen forbedres
  • Forbereder din kode til mere sofistikeret tilstandshåndtering
  • Skaber konsistente mønstre for adgang til tilstandsdata
  • Etablerer fundamentet for centraliserede tilstandsopdateringer

💡 Bemærk: Denne refaktorering løser ikke straks vores problemer, men det skaber det essentielle fundament for de kraftfulde forbedringer, der kommer!

Implementering af kontrollerede tilstandsopdateringer

Med vores tilstand centraliseret er næste skridt at etablere kontrollerede mekanismer til dataændringer. Denne tilgang sikrer forudsigelige tilstandsændringer og lettere fejlsøgning.

Det grundlæggende princip minder om lufttrafikkontrol: i stedet for at tillade flere funktioner at ændre tilstanden uafhængigt, vil vi kanalisere alle ændringer gennem en enkelt, kontrolleret funktion. Dette mønster giver klar kontrol over, hvornår og hvordan data ændres.

Uforanderlig tilstandshåndtering:

Vi vil behandle vores state-objekt som uforanderligt, hvilket betyder, at vi aldrig ændrer det direkte. I stedet skaber hver ændring et nyt tilstandsobjekt med de opdaterede data.

Selvom denne tilgang måske oprindeligt virker ineffektiv sammenlignet med direkte ændringer, giver den betydelige fordele for fejlsøgning, testning og opretholdelse af applikationens forudsigelighed.

Fordele ved uforanderlig tilstandshåndtering:

Fordel Beskrivelse Indvirkning
Forudsigelighed Ændringer sker kun gennem kontrollerede funktioner Nemmere at fejlsøge og teste
Historiksporing Hver tilstandsændring skaber et nyt objekt Muliggør fortryd/omgør funktionalitet
Forebyggelse af bivirkninger Ingen utilsigtede ændringer Forhindrer mystiske fejl
Ydelsesoptimering Let at opdage, når tilstanden faktisk ændrede sig Muliggør effektive UI-opdateringer

JavaScript-uforanderlighed med Object.freeze():

JavaScript tilbyder Object.freeze() for at forhindre ændringer af objekter:

const immutableState = Object.freeze({ account: userData });
// Any attempt to modify immutableState will throw an error

Hvad der sker her:

  • Forhindrer direkte egenskabstildelinger eller sletninger
  • Kaster undtagelser, hvis der forsøges på ændringer
  • Sikrer, at tilstandsændringer skal gå gennem kontrollerede funktioner
  • Skaber en klar kontrakt for, hvordan tilstanden kan opdateres

💡 Dybdegående: Læs om forskellen mellem overfladiske og dybe uforanderlige objekter i MDN-dokumentationen. At forstå denne forskel er afgørende for komplekse tilstandsstrukturer.

Opgave

Lad os oprette en ny updateState()-funktion:

function updateState(property, newData) {
  state = Object.freeze({
    ...state,
    [property]: newData
  });
}

I denne funktion opretter vi et nyt tilstandsobjekt og kopierer data fra den tidligere tilstand ved hjælp af spread (...) operatoren. Derefter overskriver vi en bestemt egenskab i tilstandsobjektet med de nye data ved hjælp af bracket notation [property] til tildeling. Til sidst låser vi objektet for at forhindre ændringer ved hjælp af Object.freeze(). Vi har kun egenskaben account gemt i tilstanden for nu, men med denne tilgang kan du tilføje så mange egenskaber, som du har brug for i tilstanden.

Vi opdaterer også initialiseringen af state for at sikre, at den oprindelige tilstand også er låst:

let state = Object.freeze({
  account: null
});

Derefter opdaterer vi register-funktionen ved at erstatte tildelingen state.account = result; med:

updateState('account', result);

Gør det samme med login-funktionen, og erstat state.account = data; med:

updateState('account', data);

Vi benytter også lejligheden til at løse problemet med, at kontodata ikke ryddes, når brugeren klikker på Logout.

Opret en ny funktion logout():

function logout() {
  updateState('account', null);
  navigate('/login');
}

I updateDashboard() skal du erstatte omdirigeringen return navigate('/login'); med return logout();.

Prøv at registrere en ny konto, logge ud og logge ind igen for at kontrollere, at alt stadig fungerer korrekt.

Tip: Du kan se alle tilstandsændringer ved at tilføje console.log(state) nederst i updateState() og åbne konsollen i din browsers udviklingsværktøjer.

Implementering af datapersistens

Problemet med sessionstab, som vi identificerede tidligere, kræver en persistensløsning, der opretholder brugertilstanden på tværs af browsersessioner. Dette forvandler vores applikation fra en midlertidig oplevelse til et pålideligt, professionelt værktøj.

Tænk på, hvordan atomure opretholder præcis tid, selv under strømafbrydelser, ved at gemme kritisk tilstand i ikke-flygtig hukommelse. På samme måde har webapplikationer brug for persistente lagringsmekanismer for at bevare essentielle brugerdata på tværs af browsersessioner og sideopdateringer.

Strategiske spørgsmål for datapersistens:

Før du implementerer persistens, skal du overveje disse kritiske faktorer:

Spørgsmål Bankapp-kontekst Beslutningspåvirkning
Er dataene følsomme? Kontosaldo, transaktionshistorik Vælg sikre lagringsmetoder
Hvor længe skal det opbevares? Loginstatus vs. midlertidige UI-præferencer Vælg passende lagringsvarighed
Har serveren brug for det? Autentificeringstokens vs. UI-indstillinger Bestem delingskrav

Browserlagringsmuligheder:

Moderne browsere tilbyder flere lagringsmekanismer, hver designet til forskellige anvendelser:

Primære lagrings-API'er:

  1. localStorage: Persistent Key/Value-lagring

    • Bevarer data på tværs af browsersessioner på ubestemt tid
    • Overlever genstart af browseren og computeren
    • Afgrænset til det specifikke websteds domæne
    • Perfekt til brugerpræferencer og loginstatus
  2. sessionStorage: Midlertidig sessionlagring

    • Fungerer identisk med localStorage under aktive sessioner
    • Rydder automatisk, når browserfanen lukkes
    • Ideel til midlertidige data, der ikke bør bevares
  3. HTTP-cookies: Server-delt lagring

    • Sendes automatisk med hver serveranmodning
    • Perfekt til autentificering tokens
    • Begrænset i størrelse og kan påvirke ydeevnen

Krav til dataserialisering:

Både localStorage og sessionStorage gemmer kun strenge:

// Convert objects to JSON strings for storage
const accountData = { user: 'john', balance: 150 };
localStorage.setItem('account', JSON.stringify(accountData));

// Parse JSON strings back to objects when retrieving
const savedAccount = JSON.parse(localStorage.getItem('account'));

Forståelse af serialisering:

  • Konverterer JavaScript-objekter til JSON-strenge ved hjælp af JSON.stringify()
  • Genskaber objekter fra JSON ved hjælp af JSON.parse()
  • Håndterer komplekse indlejrede objekter og arrays automatisk
  • Fejler på funktioner, udefinerede værdier og cirkulære referencer

💡 Avanceret mulighed: For komplekse offline-applikationer med store datasæt, overvej IndexedDB API. Det tilbyder en fuld klient-side database, men kræver en mere kompleks implementering.

Opgave: Implementer localStorage Persistens

Lad os implementere persistent lagring, så brugerne forbliver logget ind, indtil de eksplicit logger ud. Vi bruger localStorage til at gemme kontodata på tværs af browsersessioner.

Trin 1: Definer lagringskonfiguration

const storageKey = 'savedAccount';

Hvad denne konstant tilbyder:

  • Skaber en konsistent identifikator for vores lagrede data
  • Forebygger tastefejl i referencer til lagringsnøgler
  • Gør det nemt at ændre lagringsnøglen, hvis nødvendigt
  • Følger bedste praksis for vedligeholdelig kode

Trin 2: Tilføj automatisk persistens

Tilføj denne linje i slutningen af funktionen updateState():

localStorage.setItem(storageKey, JSON.stringify(state.account));

Hvad der sker her:

  • Konverterer kontoobjektet til en JSON-streng for lagring
  • Gemmer data ved hjælp af vores konsistente lagringsnøgle
  • Udføres automatisk, når der sker ændringer i tilstanden
  • Sikrer at lagrede data altid er synkroniseret med den aktuelle tilstand

💡 Arkitektonisk fordel: Fordi vi centraliserede alle tilstandsopdateringer gennem updateState(), krævede det kun én linje kode at tilføje persistens. Dette viser styrken ved gode arkitektoniske beslutninger!

Trin 3: Gendan tilstand ved app-opstart

Opret en initialiseringsfunktion til at gendanne gemte data:

function init() {
  const savedAccount = localStorage.getItem(storageKey);
  if (savedAccount) {
    updateState('account', JSON.parse(savedAccount));
  }

  // Our previous initialization code
  window.onpopstate = () => updateRoute();
  updateRoute();
}

init();

Forståelse af initialiseringsprocessen:

  • Henter eventuelle tidligere gemte kontodata fra localStorage
  • Parser JSON-strengen tilbage til et JavaScript-objekt
  • Opdaterer tilstanden ved hjælp af vores kontrollerede opdateringsfunktion
  • Gendanner brugerens session automatisk ved sideindlæsning
  • Udføres før ruteopdateringer for at sikre, at tilstanden er tilgængelig

Trin 4: Optimer standardruten

Opdater standardruten for at udnytte persistens:

I updateRoute(), erstat:

// Replace: return navigate('/login');
return navigate('/dashboard');

Hvorfor denne ændring giver mening:

  • Udnytter vores nye persistenssystem effektivt
  • Tillader dashboardet at håndtere autentificeringskontroller
  • Omdirigerer automatisk til login, hvis der ikke findes en gemt session
  • Skaber en mere problemfri brugeroplevelse

Test din implementering:

  1. Log ind på din bankapp
  2. Opdater browserens side
  3. Bekræft, at du forbliver logget ind og på dashboardet
  4. Luk og genåbn din browser
  5. Naviger tilbage til din app og bekræft, at du stadig er logget ind

🎉 Mål opnået: Du har med succes implementeret persistent tilstandsstyring! Din app opfører sig nu som en professionel webapplikation.

Balancering af persistens med dataaktualitet

Vores persistenssystem opretholder brugersessioner, men introducerer en ny udfordring: dataforældelse. Når flere brugere eller applikationer ændrer de samme serverdata, bliver lokal cache-information forældet.

Denne situation minder om viking-navigatører, der både stolede på gemte stjernekort og aktuelle observationer af himmellegemer. Kortene gav konsistens, men navigatørerne havde brug for friske observationer for at tage højde for ændrede forhold. På samme måde har vores applikation brug for både persistent brugertilstand og aktuelle serverdata.

🧪 Opdagelse af problemet med dataaktualitet:

  1. Log ind på dashboardet med kontoen test
  2. Kør denne kommando i en terminal for at simulere en transaktion fra en anden kilde:
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
  1. Opdater din dashboard-side i browseren
  2. Observer, om du ser den nye transaktion

Hvad denne test viser:

  • Viser hvordan localStorage kan blive "forældet" (udateret)
  • Simulerer virkelige scenarier, hvor dataændringer sker uden for din app
  • Afslører spændingen mellem persistens og dataaktualitet

Udfordringen med dataforældelse:

Problem Årsag Brugerpåvirkning
Forældede data localStorage udløber aldrig automatisk Brugere ser uaktuelle oplysninger
Serverændringer Andre apps/brugere ændrer de samme data Inkonsistente visninger på tværs af platforme
Cache vs. virkelighed Lokal cache matcher ikke servertilstanden Dårlig brugeroplevelse og forvirring

Løsningsstrategi:

Vi implementerer et "opdater ved indlæsning"-mønster, der balancerer fordelene ved persistens med behovet for friske data. Denne tilgang opretholder den glatte brugeroplevelse, samtidig med at den sikrer dataens nøjagtighed.

Opgave: Implementer dataopdateringssystem

Vi opretter et system, der automatisk henter friske data fra serveren, mens vi opretholder fordelene ved vores persistente tilstandsstyring.

Trin 1: Opret konto-dataopdaterer

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);
}

Forståelse af denne funktions logik:

  • Kontrollerer om en bruger i øjeblikket er logget ind (state.account eksisterer)
  • Omdirigerer til logout, hvis der ikke findes en gyldig session
  • Henter friske kontodata fra serveren ved hjælp af den eksisterende getAccount()-funktion
  • Håndterer serverfejl elegant ved at logge ud ugyldige sessioner
  • Opdaterer tilstanden med friske data ved hjælp af vores kontrollerede opdateringssystem
  • Udløser automatisk localStorage-persistens gennem funktionen updateState()

Trin 2: Opret dashboard-opdateringshåndtering

async function refresh() {
  await updateAccountData();
  updateDashboard();
}

Hvad denne opdateringsfunktion opnår:

  • Koordinerer dataopdatering og UI-opdateringsprocessen
  • Venter på, at friske data indlæses, før displayet opdateres
  • Sikrer at dashboardet viser de mest aktuelle oplysninger
  • Opretholder en ren adskillelse mellem datastyring og UI-opdateringer

Trin 3: Integrer med rutesystemet

Opdater din rutekonfiguration til automatisk at udløse opdatering:

const routes = {
  '/login': { templateId: 'login' },
  '/dashboard': { templateId: 'dashboard', init: refresh }
};

Hvordan denne integration fungerer:

  • Udfører opdateringsfunktionen hver gang dashboard-ruten indlæses
  • Sikrer at friske data altid vises, når brugere navigerer til dashboardet
  • Opretholder den eksisterende rutestruktur, mens dataaktualitet tilføjes
  • Tilbyder et konsistent mønster for rutespecifik initialisering

Test dit dataopdateringssystem:

  1. Log ind på din bankapp
  2. Kør curl-kommandoen fra tidligere for at oprette en ny transaktion
  3. Opdater din dashboard-side eller naviger væk og tilbage
  4. Bekræft, at den nye transaktion vises med det samme

🎉 Perfekt balance opnået: Din app kombinerer nu den glatte oplevelse af persistent tilstand med nøjagtigheden af friske serverdata!

GitHub Copilot Agent-udfordring 🚀

Brug Agent-tilstand til at fuldføre følgende udfordring:

Beskrivelse: Implementer et omfattende tilstandsstyringssystem med fortryd/gendan-funktionalitet til bankappen. Denne udfordring vil hjælpe dig med at øve avancerede tilstandsstyringskoncepter, herunder historiksporing af tilstand, uforanderlige opdateringer og synkronisering af brugergrænsefladen.

Prompt: Opret et forbedret tilstandsstyringssystem, der inkluderer: 1) En historik-array, der sporer alle tidligere tilstande, 2) Fortryd og gendan funktioner, der kan vende tilbage til tidligere tilstande, 3) UI-knapper til fortryd/gendan operationer på dashboardet, 4) En maksimal historikgrænse på 10 tilstande for at forhindre hukommelsesproblemer, og 5) Korrekt oprydning af historik, når brugeren logger ud. Sørg for, at fortryd/gendan-funktionaliteten fungerer med ændringer i kontosaldo og vedvarer på tværs af browseropdateringer.

Læs mere om agent mode her.

🚀 Udfordring: Lagringsoptimering

Din implementering håndterer nu brugersessioner, dataopdatering og tilstandsstyring effektivt. Overvej dog, om vores nuværende tilgang optimalt balancerer lagringseffektivitet med funktionalitet.

Ligesom skakmestre, der skelner mellem essentielle brikker og ofre, kræver effektiv tilstandsstyring identifikation af, hvilke data der skal bevares, og hvilke der altid skal hentes friske fra serveren.

Optimeringsanalyse:

Evaluer din nuværende localStorage-implementering og overvej disse strategiske spørgsmål:

  • Hvad er det minimale information, der kræves for at opretholde brugerautentifikation?
  • Hvilke data ændrer sig ofte nok til, at lokal caching giver lille fordel?
  • Hvordan kan lagringsoptimering forbedre ydeevnen uden at forringe brugeroplevelsen?

Implementeringsstrategi:

  • Identificer de essentielle data, der skal bevares (sandsynligvis kun brugeridentifikation)
  • Modificer din localStorage-implementering til kun at gemme kritiske sessionsdata
  • Sørg for at friske data altid hentes fra serveren ved dashboardbesøg
  • Test at din optimerede tilgang opretholder den samme brugeroplevelse

Avanceret overvejelse:

  • Sammenlign kompromiserne mellem at gemme fulde kontodata vs. kun autentifikationstokens
  • Dokumenter dine beslutninger og begrundelser for fremtidige teammedlemmer

Denne udfordring vil hjælpe dig med at tænke som en professionel udvikler, der overvejer både brugeroplevelse og applikationseffektivitet. Tag dig tid til at eksperimentere med forskellige tilgange!

Quiz efter forelæsning

Quiz efter forelæsning

Opgave

Implementer "Tilføj transaktion"-dialog

Her er et eksempelresultat efter at have fuldført opgaven:

Skærmbillede, der viser et eksempel på "Tilføj transaktion"-dialog


Ansvarsfraskrivelse:
Dette dokument er blevet oversat ved hjælp af AI-oversættelsestjenesten Co-op Translator. Selvom vi bestræber os på nøjagtighed, skal du være opmærksom på, at automatiserede oversættelser kan indeholde fejl eller unøjagtigheder. Det originale dokument på dets oprindelige sprog bør betragtes som den autoritative kilde. For kritisk information anbefales professionel menneskelig oversættelse. Vi er ikke ansvarlige for eventuelle misforståelser eller fejltolkninger, der opstår som følge af brugen af denne oversættelse.