Hvordan bygge komplekse, storskalige Vue.js-programmer med Vuex

Det er så lett å lære og bruke Vue.js at alle kan bygge et enkelt program med det rammeverket. Selv nybegynnere, ved hjelp av Vues dokumentasjon, kan gjøre jobben. Men når kompleksitet kommer inn i spillet, blir det litt mer alvorlig. Sannheten er at flere, dypt nestede komponenter med delt tilstand raskt kan gjøre søknaden din til et uoppnåelig rot.

Hovedproblemet i et komplekst program er hvordan man styrer tilstanden mellom komponenter uten å skrive spaghettikode eller produsere bivirkninger. I denne opplæringen lærer du hvordan du løser dette problemet ved å bruke Vuex: et statsadministrasjonsbibliotek for å bygge komplekse Vue.js-applikasjoner.

Hva er Vuex?

Vuex er et statsadministrasjonsbibliotek som er spesielt innstilt for å bygge komplekse, store Vue.js applikasjoner. Den bruker en global, sentralisert butikk for alle komponentene i en applikasjon, og utnytter dens reaktivitetssystem for umiddelbare oppdateringer.

Vuex-butikken er utformet på en slik måte at det ikke er mulig å endre tilstanden fra noen komponent. Dette sikrer at staten kun kan muteres på en forutsigbar måte. Dermed blir butikken din en eneste kilden til sannhet: hvert dataelement lagres kun én gang og er skrivebeskyttet for å forhindre at programmets komponenter ødelegger tilstanden som nås av andre komponenter.

Hvorfor trenger du Vuex?

Du kan spørre: Hvorfor trenger jeg Vuex i utgangspunktet? Kan jeg ikke bare sette den delte staten i en vanlig JavaScript-fil og importere den til min Vue.js-applikasjon?

Du kan selvfølgelig, men sammenlignet med et vanlig globalt objekt, Vuex-butikken har noen betydelige fordeler og fordeler:

  • Vuex-butikken er reaktiv. Når komponentene henter en tilstand fra den, vil de reaktivt oppdatere sine synspunkter hver gang staten endres.
  • Komponenter kan ikke direkte mutere butikkens tilstand. Den eneste måten å forandre butikkens tilstand på er å eksplisitt begå mutasjoner. Dette sikrer at alle tilstandsendringer etterlater en sporbar plate, noe som gjør applikasjonen enklere å feilsøke og teste.
  • Du kan enkelt feilsøke applikasjonen takket være Vuex-integrasjonen med Vue DevTools-utvidelse.
  • Vuex-butikken gir deg et fugleperspektiv av hvordan alt er forbundet og påvirket i søknaden din.
  • Det er lettere å vedlikeholde og synkronisere tilstanden mellom flere komponenter, selv om komponenthierarkiet endres.
  • Vuex gjør direkte kryss-komponentkommunikasjon mulig.
  • Hvis en komponent blir ødelagt, forblir staten i Vuex-butikken intakt.

Komme i gang med Vuex

Før vi kommer i gang, vil jeg gjøre flere ting klart. 

Først for å følge denne opplæringen må du ha en god forståelse av Vue.js og dets komponentsystem, eller i det minste minimal erfaring med rammen. 

Også, målet med denne opplæringen er ikke å vise deg hvordan du bygger et faktisk komplekst program; Målet er å fokusere mer på Vuex konsepter og hvordan du kan bruke dem til å bygge komplekse applikasjoner. Derfor skal jeg bruke veldig enkle og enkle eksempler, uten noen overflødig kode. Når du har full forståelse av Vuex-konseptene, vil du kunne bruke dem på noe nivå av kompleksitet.

Til slutt bruker jeg ES2015 syntaks. Hvis du ikke er kjent med det, kan du lære det her.

Og nå, la oss komme i gang!

Sette opp et Vuex-prosjekt

Det første trinnet for å komme i gang med Vuex er å ha Vue.js og Vuex installert på maskinen din. Det er flere måter å gjøre det på, men vi bruker den enkleste. Bare opprett en HTML-fil og legg til de nødvendige CDN-koblingene:

            

Jeg brukte noen CSS for å få komponentene til å se bedre ut, men du trenger ikke å bekymre deg for den CSS-koden. Det hjelper deg bare med å få et visuelt begrep om hva som skjer. Bare kopier og lim inn følgende inne i  stikkord:

La oss nå lage noen komponenter for å jobbe med. Inne i > tag, rett over avslutningen  tag, legg følgende Vue kode:

Vue.component ('ChildB', template: ' 

Score:

') Vue.component (' ChildA ', template:'

Score:

') Vue.component (' Parent ', template:'

Score:

') ny Vue (el:' #app ')

Her har vi en Vue-forekomst, en overordnet komponent og to barnekomponenter. Hver komponent har en overskrift "Score:"der vi skal sende ut app-staten.

Det siste du trenger å gjøre er å legge inn en innpakning

 med id = "app" rett etter åpningen , og plasser deretter den overordnede komponenten inne:

Forberedelsesarbeidet er nå gjort, og vi er klare til å fortsette.

Utforsker Vuex

Statlig ledelse

I virkeligheten behandler vi kompleksitet ved å bruke strategier for å organisere og strukturere innholdet vi vil bruke. Vi grupperer sammenhenger i forskjellige seksjoner, kategorier etc. Det er som et bokbibliotek hvor bøkene kategoriseres og settes i forskjellige seksjoner slik at vi lett kan finne det vi leter etter. Vuex arrangerer søknadsdata og logikk relatert til tilstand i fire grupper eller kategorier: stat, getters, mutasjoner og handlinger.

Stat og mutasjoner er grunnlaget for enhver Vuex-butikk:

  • stat er et objekt som holder tilstanden til søknadsdataene.
  • mutasjoner er også et objekt som inneholder metoder som påvirker staten.

Getters og handlinger er som logiske fremskrivninger av tilstand og mutasjoner:

  • getters inneholder metoder som brukes til å abstrahere tilgangen til staten, og å gjøre noen forhåndsbehandlingsjobber, om nødvendig (dataregning, filtrering etc.).
  • handlinger er metoder som brukes til å utløse mutasjoner og utføre asynkron kode.

La oss undersøke følgende diagram for å gjøre ting litt klarere:

På venstre side har vi et eksempel på en Vuex-butikk, som vi senere skal lage i denne opplæringen. På høyre side har vi et Vuex-arbeidsflytdiagram som viser hvordan de forskjellige Vuex-elementene fungerer sammen og kommuniserer med hverandre.

For å endre tilstanden må en bestemt Vue-komponent begå mutasjoner (f.eks. dette. $ store.commit ('increment', 3)), og da endrer disse mutasjonene staten (poengsum blir 3). Etter dette oppdateres getters automatisk takket være Vues reaktive system, og de gjør oppdateringene i komponentens visning (med dette. $ store.getters.score). 

Mutasjoner kan ikke utføre asynkron kode, fordi dette ville gjøre det umulig å registrere og spore endringene i feilsøkingsverktøy som Vue DevTools. For å bruke asynkron logikk må du sette den i handlinger. I dette tilfellet vil en komponent først sende handlinger (dette. $ store.dispatch ('incrementScore', 3000)) der den asynkrone koden utføres, og da vil disse handlingene begå mutasjoner, som vil mutere staten. 

Lag et Vuex Store-skjelett

Nå som vi har utforsket hvordan Vuex fungerer, la oss lage skjelettet for vår Vuex-butikk. Sett følgende kode over ChildB komponentregistrering:

const store = ny Vuex.Store (state: , getters: , mutasjoner: , handlinger: )

For å gi global tilgang til Vuex-butikken fra hver komponent, må vi legge til butikk eiendom i Vue-forekomsten:

ny Vue (el: '#app', butikk // registrere Vuex-butikken globalt)

Nå kan vi få tilgang til butikken fra hver komponent med dette. $ butikken variabel.

Så langt, hvis du åpner prosjektet med CodePen i nettleseren, bør du se følgende resultat.

Statlige egenskaper

Statobjektet inneholder alle de delte dataene i søknaden din. Selvfølgelig, hvis det er nødvendig, kan hver komponent også ha sin egen private stat.

Tenk deg at du vil bygge et spillprogram, og du trenger en variabel for å lagre spillets poengsum. Så du legger det i statens objekt:

tilstand: poengsum: 0

Nå kan du få tilgang til statens poengsum direkte. La oss gå tilbake til komponentene og gjenbruk dataene fra butikken. For å kunne gjenbruke reaktive data fra butikkens tilstand, bør du bruke beregnede egenskaper. Så la oss lage en score () Beregnet eiendom i foreldrekomponenten:

beregnes: score () returnere dette. $ store.state.score

I moderkomponentens mal legger du score uttrykk:

Resultat: score

Og nå, gjør det samme for de to barns komponentene.

Vuex er så smart at det vil gjøre alt arbeidet for oss å reagere oppdatere poengsum eiendom når staten endres. Prøv å endre poengsummen og se hvordan resultatet oppdateres i alle tre komponentene.

Opprette Getters

Det er selvfølgelig bra at du kan gjenbruke dette. $ store.state Søkeord inne i komponentene, som du så over. Men tenk på følgende scenarier:

  1. I en stor applikasjon, hvor flere komponenter får tilgang til butikkens tilstand ved å bruke dette. $ store.state.score, du bestemmer deg for å endre navnet på poengsum. Dette betyr at du må endre navnet på variabelen inne i hver komponent som bruker den! 
  2. Du vil bruke en beregnet verdi av staten. For eksempel, la oss si at du vil gi spillere en bonus på 10 poeng når poengsummen når 100 poeng. Så når poengsummen treffer 100 poeng, blir 10 poeng bonus lagt til. Dette betyr at hver komponent må inneholde en funksjon som gjenoppretter poengsummen og øker den med 10. Du vil ha gjentatt kode i hver komponent, noe som ikke er bra i det hele tatt!

Heldigvis tilbyr Vuex en arbeidsløsning for å håndtere slike situasjoner. Tenk deg den sentraliserte getteren som får tilgang til butikkens tilstand og gir en getter-funksjon til hver av statens gjenstander. Om nødvendig kan denne getteren bruke noen beregning til statens gjenstand. Og hvis du trenger å endre navn på noen av statens egenskaper, endrer du dem bare på ett sted, i denne getteren. 

La oss lage en score () getter:

getters: score (state) return state.score

En getter mottar stat som sin første argument, og bruker den til å få tilgang til statens egenskaper.

Merk: Getters mottar også getters som det andre argumentet. Du kan bruke den til å få tilgang til de andre getters i butikken.

I alle komponenter, endre score () beregnet eiendom for å bruke score () getter i stedet for statens score direkte.

beregnet: score () returnere dette. $ store.getters.score

Nå, hvis du bestemmer deg for å endre poengsum til resultat, du må oppdatere den bare på ett sted: i score () getter. Prøv det i denne CodePen!

Opprette mutasjoner

Mutasjoner er den eneste tillatte måten å endre tilstanden på. Utløsende endringer betyr bare å begå mutasjoner i komponentmetoder.

En mutasjon er ganske mye en hendelseshåndteringsfunksjon som er definert av navn. Muteringshåndteringsfunksjoner mottar a stat som et første argument. Du kan også passere et ekstra andre argument, som kalles nyttelast for mutasjonen. 

La oss lage en inkrement () mutasjon:

mutasjoner: increment (state, step) state.score + = trinn

Mutasjoner kan ikke kalles direkte! For å utføre en mutasjon, bør du ringe begå() metode med navnet på den tilsvarende mutasjonen og mulige tilleggsparametere. Det kan være bare en, som skritt i vårt tilfelle, eller det kan være flere som er pakket inn i et objekt.

La oss bruke inkrement () mutasjon i de to barnets komponenter ved å opprette en metode som heter changeScore ():

metoder: changeScore () this. $ store.commit ('increment', 3); 

Vi begår en mutasjon i stedet for å endre dette. $ store.state.score direkte fordi vi vil eksplisitt spore endringen som er gjort av mutasjonen. På den måten gjør vi vår logikk logikk mer gjennomsiktig, sporbar, og lett å begrunnelse om. I tillegg gjør det det mulig å implementere verktøy, som Vue DevTools eller Vuetron, som kan logge alle mutasjoner, ta statlige stillbilder og utføre tidsreiser feilsøking.

La oss nå sette changeScore () metode i bruk. I hver mal av de to barnekomponentene lager du en knapp og legger til en klikkhendelselytter på den:

Når du klikker på knappen, vil staten bli økt med 3, og denne endringen reflekteres i alle komponentene. Nå har vi effektivt oppnådd direkte kryss-komponentkommunikasjon, noe som ikke er mulig med Vue.js innebygde "rekvisitter ned, hendelser opp" -mekanismen. Sjekk det ut i vårt CodePen eksempel.

Opprette handlinger

En handling er bare en funksjon som forplikter seg til en mutasjon. Det endrer staten indirekte, noe som gjør det mulig å utføre asynkrone operasjoner. 

La oss lage en incrementScore () handling:

handlinger: incrementScore: (commit, delay) => setTimeout (() => commit ('increment', 3), forsinkelse)

Handlinger får kontekst som den første parameteren, som inneholder alle metoder og egenskaper fra butikken. Vanligvis trekker vi bare ut de delene vi trenger ved å bruke ES2015-argumentdestrukturering. De begå metoden er en vi trenger veldig ofte. Handlinger får også et nytt nyttelastargument, akkurat som mutasjoner.

ChildB komponent, endre changeScore () metode:

metoder: changeScore () this. $ store.dispatch ('incrementScore', 3000); 

For å ringe en handling bruker vi avsendelse () metode med navnet på den tilsvarende handlingen og tilleggsparametre, akkurat som med mutasjoner.

Nå, den Endre Score knapp fra childâ komponenten vil øke poengsummen med 3. Den samme knappen fra ChildB komponenten vil gjøre det samme, men etter en forsinkelse på 3 sekunder. I det første tilfellet utfører vi synkron kode, og vi bruker en mutasjon, men i andre tilfelle utfører vi asynkron kode, og vi må bruke en handling i stedet. Se hvordan alt fungerer i vårt CodePen-eksempel.

Vuex Mapping Helpers

Vuex tilbyr noen nyttige hjelpere som kan strømline prosessen med å skape stat, getters, mutasjoner og handlinger. I stedet for å skrive disse funksjonene manuelt, kan vi fortelle Vuex å lage dem for oss. La oss se hvordan det fungerer.

I stedet for å skrive score () Beregnet eiendom som denne:

beregnes: score () returnere dette. $ store.state.score

Vi bruker bare mapState () hjelper slik:

beregnes: ... Vuex.mapState (['score'])

Og score () Egenskapen er opprettet automatisk for oss.

Det samme gjelder for getters, mutasjoner og handlinger. 

Å opprette score () getter, bruker vi mapGetters () hjelper:

beregnes: ... Vuex.mapGetters (['score'])

Å opprette changeScore () metode, bruker vi mapMutations () hjelper slik:

metoder: ... Vuex.mapMutations (changeScore: 'increment')

Når det brukes til mutasjoner og handlinger med nyttelastargumentet, må vi passere det argumentet i malen der vi definerer hendelseshandleren:

Hvis vi vil changeScore () å bruke en handling i stedet for en mutasjon, bruker vi mapActions () som dette:

metoder: ... Vuex.mapActions (changeScore: 'incrementScore')

Igjen må vi definere forsinkelsen i hendelseshandleren:

Merk: Alle kartleggingshjelpere returnerer et objekt. Så, hvis vi vil bruke dem i kombinasjon med andre lokale beregnede egenskaper eller metoder, må vi slå dem sammen i ett objekt. Heldigvis, med objektet spredt operatør (... ), kan vi gjøre det uten å bruke noe verktøy. 

I vår CodePen kan du se et eksempel på hvordan alle kartleggingshjelpere brukes i praksis.

Gjør butikken mer modulær

Det ser ut til at problemet med kompleksitet stadig hindrer vår vei. Vi løst det før ved å skape Vuex-butikken, hvor vi gjorde enkeltforvaltningen og komponentkommunikasjonen enkel. I den butikken har vi alt på ett sted, lett å manipulere og lett å begrunne. 

Men som vår søknad vokser, blir denne lettbehandlede butikkfilen større og større og som følge derav vanskeligere å vedlikeholde. Igjen, vi trenger noen strategier og teknikker for å forbedre applikasjonsstrukturen ved å returnere den til sin enkle å vedlikeholde form. I denne delen vil vi utforske flere teknikker som kan hjelpe oss med dette forpliktelsen.

Bruke Vuex-moduler

Vuex tillater oss å dele butikkobjektet i separate moduler. Hver modul kan inneholde sin egen tilstand, mutasjoner, handlinger, getters og andre nestede moduler. Etter at vi har opprettet de nødvendige modulene, registrerer vi dem i butikken.

La oss se det i aksjon:

const childB = state: result: 3, getters: resultat (tilstand) return state.result, mutasjoner: økning (tilstand, trinn) state.result + = step, handlinger: increaseResult : commit, delay => setTimeout (() => commit 'increase', 6, forsinkelse) const childA = state: score: 0, getters: score state scor + = trinn, handlinger: incrementScore: commit, delay) => setTimeout (() => commit 'increment', 3, forsinkelse) const store = ny Vuex.Store (moduler: scoreBoard: childA, resultBoard: childB)

I eksempelet ovenfor opprettet vi to moduler, ett for hver barnekomponent. Modulene er bare enkle objekter, som vi registrerer som Scoreboard og resultBoard i moduler objekt inne i butikken. Koden for childâ er det samme som i butikken fra tidligere eksempler. I koden for childB, Vi legger til noen endringer i verdier og navn.

La oss nå finjustere ChildB komponent for å gjenspeile endringene i resultBoard modul. 

Vue.component ('ChildB', template: ' 

Resultat: result

', beregnet: result () return this. $ store.getters.result, metoder: changeResult () this. $ store.dispatch (' increaseResult ', 3000); )

childâ komponent, det eneste vi trenger å modifisere er changeScore () metode:

Vue.component ('ChildA', template: ' 

Resultat: score

', beregnet: score () returnere dette. $ store.getters.score, metoder: changeScore () this. $ store.dispatch (' incrementScore ', 3000); )

Som du kan se, splitting butikken i moduler gjør det mye mer lett og vedlikeholdsbar, samtidig som den holder sin flotte funksjonalitet. Ta en titt på den oppdaterte CodePen for å se den i aksjon.

Namespaced Modules

Hvis du vil ha eller trenger å bruke ett og samme navn for en bestemt egenskap eller metode i modulene dine, bør du vurdere navneskalering av dem. Ellers kan du observere noen merkelige bivirkninger, for eksempel å utføre alle handlinger med samme navn, eller få feil tilstands verdier. 

For å navngi en Vuex-modul, har du bare satt inn namespaced eiendom til ekte.

const childB = namespaced: true, state: poengsum: 3, getters: score (tilstand) return state.score, mutasjoner: inkrement (stat, trinn) state.score + = step, handlinger: incrementScore: commit, delay) => setTimeout (() => commit 'increment', 6 forsinkelse const childA = namespaced: true, state: score: 0, getters: score (state) return state.score, mutasjoner: inkrement (tilstand, trinn) state.score + = trinn, handlinger: incrementScore: commit, delay) = > setTimeout (() => commit ('increment', 3), forsinkelse)

I eksemplet ovenfor gjorde vi egenskaps- og metodenavnene de samme for de to modulene. Og nå kan vi bruke en eiendom eller metode prefiks med navnet på modulen. For eksempel, hvis vi vil bruke score () getter fra resultBoard modul, skriver vi det slik: resultBoard / resultatet. Hvis vi vil score () getter fra Scoreboard modul, så skriver vi det slik: resultattavle / resultatet

La oss nå endre komponentene våre for å gjenspeile endringene vi har gjort. 

Vue.component ('ChildB', template: ' 

Resultat: result

', beregnet: resultat () returnere dette. $ store.getters [' resultBoard / score '], metoder: changeResult () this. $ store.dispatch (' resultBoard / incrementScore ', 3000); ) Vue.component ('ChildA', template: '

Resultat: score

', beregnet: score () returnere dette. $ store.getters [' scoreBoard / score '], metoder: changeScore () this. $ store.dispatch (' scoreBoard / incrementScore ', 3000); )

Som du kan se i vårt CodePen eksempel, kan vi nå bruke metoden eller eiendommen vi ønsker og få det resultatet vi forventer.

Å splitte Vuex-butikken i separate filer

I den forrige delen forbedret vi applikasjonsstrukturen til en viss grad ved å skille butikken i moduler. Vi gjorde butikken renere og mer organisert, men likevel ligger alle butikkodene og modulene i samme store fil. 

Så det neste logiske trinnet er å dele Vuex-butikken i separate filer. Tanken er å ha en individuell fil for butikken selv og en for hver av sine objekter, inkludert modulene. Dette betyr at du har separate filer for staten, getters, mutasjoner, handlinger og for hver enkelt modul (store.jsstate.js, getters.js, etc.) Du kan se et eksempel på denne strukturen på slutten av neste avsnitt.

Bruke Vue Single File Components

Vi har gjort Vuex-butikken så modulær som mulig. Det neste vi kan gjøre er å bruke den samme strategien til Vue.js-komponentene også. Vi kan sette hver komponent i en enkelt, selvstendig fil med en .vue forlengelse. For å lære hvordan dette virker, kan du besøke dokumentasjonssiden for Vue Single File Components. 

Så, i vårt tilfelle, har vi tre filer: Parent.vueChildA.vue, og ChildB.vue

Til slutt, hvis vi kombinerer alle tre teknikkene, vil vi ende opp med følgende eller lignende struktur:

├── index.html └── src ├── main.js ├── App.vue ├── komponenter │ ├── Parent.vue │ ├── ChildA.vue │ ├── ChildB.vue └── butikk ├── store.js ├── state.js ├── getters.js ├── mutations.js ├── actions.js └── moduler ├── childA.js └── childB.js

I vår veiledning GitHub repo kan du se det fullførte prosjektet med strukturen ovenfor.

oppsummering

La oss ta opp noen hovedpunkter du trenger å huske om Vuex:

Vuex er et statsadministrasjonsbibliotek som hjelper oss med å bygge komplekse, store applikasjoner. Den bruker en global, sentralisert butikk for alle komponentene i en applikasjon. For å abstrahere staten bruker vi getters. Getters er omtrent like beregnede egenskaper og er en ideell løsning når vi må filtrere eller regne ut noe på kjøretid.

Vuex-butikken er reaktiv, og komponenter kan ikke direkte mutere butikkens tilstand. Den eneste måten å mutere staten på er å begå mutasjoner, som er synkrone transaksjoner. Hver mutasjon skal utføre bare én handling, må være så enkel som mulig, og er bare ansvarlig for å oppdatere et stykke av staten.

Asynkron logikk bør innkapsles i handlinger. Hver handling kan begå en eller flere mutasjoner, og en mutasjon kan begås av mer enn én handling. Handlinger kan være komplekse, men de endrer aldri staten direkte.

Endelig er modularitet nøkkelen til vedlikehold. For å håndtere kompleksitet og lage kodekode modulær bruker vi "divide and conquer" -prinsippet og kodesplitteteknikken.

Konklusjon

Det er det! Du vet allerede de viktigste konseptene bak Vuex, og du er klar til å begynne å bruke dem i praksis.  

For korthet og enkelhets skyld har jeg forsettlig utelatt noen detaljer og funksjoner i Vuex, så du må lese hele Vuex-dokumentasjonen for å lære alt om Vuex og dets funksjonssett.