Cum se construiesc aplicații complexe Vue.js pe scară largă cu Vuex

Este atât de ușor de învățat și de utilizat Vue.js că oricine poate construi o aplicație simplă cu acel cadru. Chiar și începători, cu ajutorul documentației lui Vue, pot face treaba. Cu toate acestea, când complexitatea intră în joc, lucrurile devin puțin mai serioase. Adevărul este că componentele multiple, adânc imbricate, cu starea partajată, pot transforma rapid aplicația într-o dezordine neîncetată.

Principala problemă într-o aplicație complexă este modul de gestionare a stării între componente fără scrierea de cod spaghete sau producerea de efecte secundare. În acest tutorial veți învăța cum să rezolvați această problemă utilizând Vuex: o bibliotecă de gestionare a statului pentru a construi aplicații complexe Vue.js.

Ce este Vuex?

Vuex este o bibliotecă de management de stat special adaptată pentru a construi aplicații Vue.js complexe și de mari dimensiuni. Utilizează un magazin global, centralizat pentru toate componentele dintr-o aplicație, profitând de sistemul său de reactivitate pentru actualizări instantanee.

Magazinul Vuex este proiectat astfel încât să nu poată fi schimbat starea de la orice componentă. Acest lucru asigură că statul poate fi mutat într-un mod previzibil. Astfel, magazinul dvs. devine o singură sursă de adevăr: fiecare element de date este stocat o singură dată și este read-only pentru a împiedica componentele aplicației să corupe statul accesat de alte componente.

De ce ai nevoie de Vuex?

Puteți întreba: De ce am nevoie de Vuex în primul rând? Nu pot pune starea partajată într-un fișier JavaScript obișnuit și îl pot importa în aplicația Vue.js?

Puteți, desigur, comparativ cu un obiect global simplu, magazinul Vuex are câteva avantaje și beneficii semnificative:

  • Magazinul Vuex este reactiv. Odată ce componentele recuperează o stare de la ea, ei își vor actualiza reacțiile în mod reactiv de fiecare dată când se schimbă starea.
  • Componentele nu pot muta direct starea magazinului. Singura modalitate de a schimba starea magazinului este prin comiterea explicită a mutațiilor. Acest lucru asigură că fiecare schimbare de stare lasă o înregistrare tracabilă, ceea ce face aplicația mai ușoară de depanare și testare.
  • Puteți depana cu ușurință aplicația datorită integrării Vuex cu extensia DevTools a Vue.
  • Magazinul Vuex vă oferă o viziune a păsărilor asupra modului în care totul este conectat și afectat în aplicația dvs..
  • Este mai ușor să întrețineți și să sincronizați starea între mai multe componente, chiar dacă ierarhia componentei se modifică.
  • Vuex face posibilă comunicarea directă între ele.
  • Dacă o componentă este distrusă, starea din magazinul Vuex va rămâne intactă.

Noțiuni de bază cu Vuex

Înainte de a începe, vreau să clarific câteva lucruri. 

În primul rând, pentru a urma acest tutorial, trebuie să aveți o bună înțelegere a Vue.js și a componentelor sale sistem, sau cel puțin experiență minimă cu cadru. 

De asemenea, scopul acestui tutorial nu este de a vă arăta cum să construiți o aplicație complexă reală; scopul este să vă concentrați mai mult atenția asupra conceptelor Vuex și cum le puteți folosi pentru a construi aplicații complexe. Din acest motiv, voi folosi exemple foarte simple și simple, fără nici un cod redundant. Odată ce înțelegeți în totalitate conceptele Vuex, veți putea să le aplicați pe orice nivel de complexitate.

În cele din urmă, voi folosi sintaxa ES2015. Dacă nu sunteți familiarizat cu aceasta, puteți afla aici.

Și acum, să începem!

Configurarea unui proiect Vuex

Primul pas pentru a începe cu Vuex este să aveți Vue.js și Vuex instalate pe mașina dvs. Există mai multe modalități de a face acest lucru, dar vom folosi cel mai ușor. Doar creați un fișier HTML și adăugați linkurile CDN necesare:

            

Am folosit câteva CSS pentru a face componentele să arate mai bine, dar nu trebuie să vă faceți griji cu privire la acest cod CSS. Vă ajută doar să obțineți o noțiune vizuală despre ceea ce se întâmplă. Doar copiați și lipiți următoarele în interiorul  etichetă:

Acum, să creăm unele componente cu care să lucrăm. În interiorul > etichetă, chiar deasupra închiderii  tag, puneți următorul cod Vue:

Vue.component ('ChildB', template: ' 

Scor:

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

Scor:

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

Scor:

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

Aici avem o instanță Vue, o componentă părinte și două componente copil. Fiecare componentă are o rubrică "Scor:"unde vom scoate starea aplicației.

Ultimul lucru pe care trebuie să-l faceți este să puneți un ambalaj

 cu id = "app" imediat după deschidere , și apoi plasați componenta părinte în interiorul:

Lucrările de pregătire se fac acum și suntem gata să mergem mai departe.

Explorând Vuex

Managementul statului

În viața reală, ne ocupăm de complexitate prin utilizarea strategiilor de organizare și structurare a conținutului pe care dorim să îl folosim. Grupăm lucrurile înrudite împreună în diferite secțiuni, categorii etc. Este ca o bibliotecă de cărți, în care cărțile sunt clasificate și plasate în secțiuni diferite, astfel încât să putem găsi cu ușurință ceea ce căutăm. Vuex aranjează datele și logica aplicațiilor legate de stat în patru grupe sau categorii: stat, getters, mutații și acțiuni.

Statul și mutațiile sunt baza pentru orice magazin Vuex:

  • stat este un obiect care păstrează starea datelor aplicației.
  • mutații este, de asemenea, un obiect care conține metode care afectează starea.

Getters și acțiunile sunt ca proiecțiile logice ale stării și ale mutațiilor:

  • getteri să conțină metode utilizate pentru a abroga accesul la stat și să facă anumite activități de preprocesare, dacă este necesar (calculul datelor, filtrarea etc.).
  • acţiuni sunt metode utilizate pentru a declanșa mutații și a executa cod asincron.

Să explorăm următoarea diagramă pentru a face lucrurile puțin mai clare:

În partea stângă, avem un exemplu de magazin Vuex, pe care îl vom crea mai târziu în acest tutorial. În partea dreaptă, avem o diagramă a fluxului de lucru Vuex, care arată modul în care diferitele elemente Vuex lucrează împreună și comunică între ele.

Pentru a schimba starea, o anumită componentă Vue trebuie să comită mutații (de ex. acest lucru. $ store.commit ('increment', 3)), iar apoi, aceste mutații schimbă starea (scor devine 3). După aceasta, getterii sunt actualizați automat datorită sistemului reactiv al Vue și fac actualizările în viziunea componentei (cu acest lucru. $ store.getters.score). 

Mutațiile nu pot executa cod asincron, deoarece acest lucru ar face imposibilă înregistrarea și urmărirea modificărilor în instrumentele de depanare, cum ar fi Vue DevTools. Pentru a utiliza logica asincronă, trebuie să o puneți în acțiune. În acest caz, o componentă va trimite mai întâi acțiuni (acest lucru. $ store.dispatch ('incrementScore', 3000)) unde codul asincron este executat, iar apoi acele acțiuni vor comite mutații, care vor muta starea. 

Creați un schelet Vuex Store

Acum că am explorat cum funcționează Vuex, să creăm scheletul pentru magazinul nostru Vuex. Puneți următorul cod deasupra ChildB înmatricularea componentelor:

const stoc = nou Vuex.Store (state: , getters: , mutații: , acțiuni: )

Pentru a oferi acces global la magazinul Vuex din fiecare componentă, trebuie să adăugăm magazin proprietate în instanța Vue:

nou Vue (el: '#app', magazin // înregistrați magazinul Vuex la nivel global)

Acum, putem accesa magazinul de la fiecare componentă cu acest lucru. magazin $ variabil.

Până în prezent, dacă deschideți proiectul cu CodePen în browser, ar trebui să vedeți următorul rezultat.

Proprietăți de stat

Obiectul de stare conține toate datele partajate din aplicația dvs. Desigur, dacă este necesar, fiecare componentă poate avea și propriul său stat privat.

Imaginați-vă că doriți să construiți o aplicație de joc și aveți nevoie de o variabilă pentru a stoca scorul jocului. Așa că ați pus-o în obiectul de stat:

stat: scor: 0

Acum puteți accesa direct scorul statului. Să ne întoarcem la componente și să reutilizăm datele din magazin. Pentru a putea reutiliza datele reactive din starea magazinului, trebuie să utilizați proprietățile calculate. Deci, să creăm a scor() proprietate calculată în componenta părinte:

calculat: scor () retur această. $ store.state.score

În șablonul componentei părinte, puneți scor expresie:

Scor: scor

Și acum, faceți același lucru pentru cele două componente ale copilului.

Vuex este atât de inteligent încât va face tot munca pentru a ne actualiza în mod reactiv scor proprietate ori de câte ori se schimbă statul. Încercați să modificați valoarea scorului și să vedeți cum se actualizează rezultatul în toate cele trei componente.

Crearea Getters

Este, desigur, bine că puteți reutiliza acest lucru. $ store.state cuvânt cheie din interiorul componentelor, după cum ați văzut mai sus. Dar imaginați-vă următoarele scenarii:

  1. Într-o aplicație la scară largă, în care mai multe componente accesează starea magazinului utilizând acest lucru. $ store.state.score, decideți să schimbați numele scor. Aceasta înseamnă că trebuie să schimbați numele variabilei din interiorul fiecărei componente care o folosește! 
  2. Doriți să utilizați o valoare calculată a statului. De exemplu, să presupunem că doriți să oferiți jucătorilor un bonus de 10 puncte atunci când scorul ajunge la 100 de puncte. Deci, când scorul atinge 100 de puncte, se adaugă bonus de 10 puncte. Aceasta înseamnă că fiecare componentă trebuie să conțină o funcție care reutilizează scorul și o mărește cu 10. Veți avea cod repetat în fiecare componentă, ceea ce nu este deloc bun!

Din fericire, Vuex oferă o soluție de lucru pentru a rezolva astfel de situații. Imaginați-vă că getter-ul centralizat care accesează starea magazinului și oferă o funcție getter pentru fiecare dintre elementele statului. Dacă este necesar, acest getter poate aplica unele calcule elementului statului. Și dacă trebuie să schimbați numele unor proprietăți ale statului, le schimbați doar într-un singur loc, în acest getter. 

Să creăm a scor() getter:

getters: scor (stat) return state.score

Un getter primește stat ca primul său argument, și apoi îl folosește pentru a accesa proprietățile statului.

Notă: primiți și Getters getteri ca al doilea argument. Puteți să-l utilizați pentru a accesa celelalte getters din magazin.

În toate componentele, modificați scor() calculat proprietate pentru a utiliza scor() getter în loc de scorul statului direct.

calculat: scor () returnați acest. $ store.getters.score

Acum, dacă decideți să schimbați scor la rezultat, trebuie să îl actualizați numai într - un singur loc: în scor() getter. Încercați-l în acest CodePen!

Crearea de mutații

Mutațiile sunt singura modalitate permisă de a schimba starea. Schimbările declanșatoare înseamnă pur și simplu comiterea mutațiilor în metodele componente.

O mutație este destul de mult o funcție de manipulare a evenimentului care este definită de nume. Funcțiile de manipulare a mutatiilor primesc a stat ca un prim argument. Puteți trece și un al doilea argument suplimentar, care se numește încărcătură utilă pentru mutație. 

Să creăm un creştere() mutaţie:

mutații: increment (state, step) state.score + = pas

Mutațiile nu pot fi chemați direct! Pentru a efectua o mutație, trebuie să apelați comite () cu numele mutației corespunzătoare și a eventualelor parametri suplimentari. S-ar putea să fie doar unul, cum ar fi Etapa în cazul nostru, sau ar putea exista mai multe înfășurate într-un obiect.

Să folosim creştere() mutație în cele două componente ale copilului prin crearea unei metode numite changeScore ():

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

Facem o mutație în loc să ne schimbăm acest lucru. $ store.state.score direct, pentru că vrem să urmărim în mod explicit schimbarea făcută de mutație. În acest fel, facem logica aplicației mai transparentă, mai ușor de urmărit și ușor de explicat. În plus, face posibilă implementarea unor instrumente, cum ar fi Vue DevTools sau Vuetron, care pot să înregistreze toate mutațiile, să efectueze instantanee de stare și să efectueze depanarea călătoriilor în timp.

Acum, hai să o punem changeScore () în utilizare. În fiecare șablon al celor două componente copil, creați un buton și adăugați un ascultător de evenimente clic pe acesta:

Când faceți clic pe buton, starea va fi incrementată cu 3, iar această modificare va fi reflectată în toate componentele. Acum am reușit să obținem o comunicare directă directă a componentelor, ceea ce nu este posibil cu mecanismul Vue.js încorporat "mecanismul de susținere a evenimentelor". Consultați-o în exemplul nostru CodePen.

Crearea de acțiuni

O acțiune este doar o funcție care comite o mutație. Modifică indirect starea, ceea ce permite executarea operațiilor asincrone. 

Să creăm un incrementScore () acțiune:

acțiuni: incrementScore: (commit, întârziere) => setTimeout (() => commit ('increment', 3)

Acțiunile obțin context ca primul parametru, care conține toate metodele și proprietățile din magazin. De obicei, extragem doar piesele de care avem nevoie folosind distrugerea argumentului ES2015. comite metoda este una de care avem nevoie foarte des. Acțiunile primesc, de asemenea, un al doilea argument privind sarcina utilă, la fel ca mutațiile.

În ChildB componentă, modificați changeScore () metodă:

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

Pentru a apela o acțiune, folosim expediere() metoda cu numele acțiunii corespunzătoare și parametrii suplimentari, la fel ca în cazul mutațiilor.

Acum Scorul de schimbare butonul din ChildA componenta va crește scorul cu 3. Butonul identic din ChildB componenta va face același lucru, dar după o întârziere de 3 secunde. În primul caz, executăm cod sincron și folosim o mutație, dar în al doilea caz executăm cod asincron și trebuie să folosim o acțiune în schimb. Vedeți cum funcționează toate în exemplul nostru CodePen.

Vuex Mapping Helpers

Vuex oferă niște ajutoare utile, care pot simplifica procesul de creare a statului, a primirii, a mutațiilor și a acțiunilor. În loc să scriem manual aceste funcții, le putem spune Vuexului să le creeze pentru noi. Să vedem cum funcționează.

În loc să scrie scor() astfel de proprietăți:

calculat: scor () retur această. $ store.state.score

Noi doar folosim mapState () ajutor ca acesta:

calculat: ... Vuex.mapState (['scor'])

Si scor() proprietatea este creată automat pentru noi.

Același lucru este valabil pentru getters, mutații și acțiuni. 

Pentru a crea scor() getter, folosim mapGetters () ajutor:

calculat: ... Vuex.mapGetters (['scor'])

Pentru a crea changeScore () metoda, folosim mapMutations () ajutor ca acesta:

metode: ... Vuex.mapMutations (changeScore: 'increment'

Atunci când se utilizează pentru mutații și acțiuni cu argumentul încărcăturii utile, trebuie să trecem acel argument în șablonul în care definim tratarea evenimentului:

Dacă vrem changeScore () pentru a folosi o acțiune în loc de o mutație, folosim mapActions () asa:

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

Din nou, trebuie să definim întârzierea procesatorului de evenimente:

Notă: Toți utilizatorii de cartografiere returnează un obiect. Deci, dacă vrem să le folosim în combinație cu alte proprietăți sau metode locale calculate, trebuie să le îmbinăm într-un singur obiect. Din fericire, cu operatorul de împrăștiere a obiectelor (... ), putem face acest lucru fără a folosi nici un utilitar. 

În Codul nostru, puteți vedea un exemplu despre modul în care toți utilizatorii de cartografiere sunt utilizați în practică.

Efectuarea unui magazin mai modular

Se pare că problema cu complexitatea ne împiedică constant drumul. Am rezolvat-o înainte prin crearea magazinului Vuex, unde am reușit să facem ușor administrarea și gestionarea componentelor de stat. În acest magazin, avem totul într-un singur loc, ușor de manipulat și ușor de gândit. 

Cu toate acestea, pe măsură ce aplicația noastră crește, acest fișier de stocare ușor de administrat devine tot mai mare și, în consecință, este mai greu de întreținut. Din nou, avem nevoie de câteva strategii și tehnici de îmbunătățire a structurii aplicațiilor, returnându-le în forma lor ușor de întreținut. În această secțiune, vom examina mai multe tehnici care ne pot ajuta în această angajament.

Utilizarea modulelor Vuex

Vuex ne permite să împărțim obiectul de stocare în module separate. Fiecare modul poate conține propriile sale stări, mutații, acțiuni, getteri și alte module imbricate. După ce am creat modulele necesare, le vom înregistra în magazin.

Să o vedem în acțiune:

const    : (commit, întârziere) => setTimeout (() => commit () (state_score + = step, acțiuni: incrementScore: (commit, întârziere) => setTimeout (() => commit ('increment', 3), întârziere) const store = nou Vuex.Store (modules: scoreBoard: childA, resultBoard: childB)

În exemplul de mai sus, am creat două module, câte unul pentru fiecare componentă copil. Modulele sunt doar obiecte simple, pe care le înregistrăm ca Tablou de bord al și resultBoard în module obiect în interiorul magazinului. Codul pentru childA este aceeași cu cea din magazin din exemplele anterioare. În codul pentru childB, adăugăm câteva modificări în valori și nume.

Acum să tweak ChildB componentă pentru a reflecta modificările din resultBoard modul. 

Vue.component ('ChildB', template: ' 

Rezultat: rezultat

', calculat: result () retur asta $ store.getters.result, metode: changeResult () this. $ store.dispatch (' increaseResult ', 3000); )

În ChildA componenta, singurul lucru pe care trebuie să îl modificăm este changeScore () metodă:

Vue.component ("ChildA", template: ' 

Scor: scor

', calculat: scor () retur asta $ store.getters.score, metode: changeScore () this. $ store.dispatch (' incrementScore ', 3000); )

După cum puteți vedea, împărțirea magazinului în module o face mult mai ușoară și mai ușor de întreținut, păstrând în același timp funcționalitatea excelentă. Consultați Codul actualizat pentru ao vedea în acțiune.

Module cu numere

Dacă doriți sau trebuie să utilizați unul și același nume pentru o anumită proprietate sau o metodă în module, atunci ar trebui să luați în considerare formarea numelor acestora. În caz contrar, puteți observa unele efecte secundare ciudate, cum ar fi executarea tuturor acțiunilor cu aceleași nume sau obținerea valorilor greșite ale statului. 

Pentru un spațiu de nume un modul Vuex, trebuie doar să setați namespaced proprietate la Adevărat.

const  , (state.score + = step,  : incrementScore: (commit, întârziere) => setTimeout (() => commit ('increment', 6), întârziere) const childA = namespaced: 0, getters: scor (state) return state.score, mutații: increment (state, step) state.score + = step, acțiuni: incrementScore: commit > setTimeout (() => commit ('increment', 3), întârziere)

În exemplul de mai sus, am făcut numele proprietăților și metodelor aceleași pentru cele două module. Și acum putem folosi o proprietate sau o metodă prefixată cu numele modulului. De exemplu, dacă vrem să folosim scor() getter de la resultBoard modul, îl tastăm astfel: resultBoard / scor. Dacă vrem scor() getter de la Tablou de bord al modul, apoi tastăm astfel: Tablou de bord al / scor

Să modificăm componentele noastre pentru a reflecta modificările pe care le-am făcut. 

Vue.component ('ChildB', template: ' 

Rezultat: rezultat

', calculat: result () retur acest $ store.getters [' resultBoard / score '], metode: changeResult () this. $ store.dispatch (' resultBoard / incrementScore ', 3000); ) Vue.component ("ChildA", template: '

Scor: scor

', calculat: scor () return this. $ store.getters [' scoreBoard / scor '], metode: changeScore () this. $ store.dispatch (' scoreBoard / incrementScore ', 3000); )

După cum puteți vedea în exemplul nostru CodePen, putem folosi acum metoda sau proprietatea dorită și obținem rezultatul pe care îl așteptăm.

Împărțirea magazinului Vuex în fișiere separate

În secțiunea anterioară, am îmbunătățit structura aplicației într-o anumită măsură separând magazia în module. Am făcut magazinul mai curat și mai organizat, dar totuși tot codul magazinului și modulele acestuia se află în același fișier mare. 

Deci, următorul pas logic este de a împărți magazinul Vuex în fișiere separate. Ideea este să aveți un fișier individual pentru magazin și unul pentru fiecare dintre obiectele sale, inclusiv modulele. Aceasta înseamnă că aveți fișiere separate pentru stat, getters, mutații, acțiuni și pentru fiecare modul individual (store.jsstate.js, getters.js, etc.) Puteți vedea un exemplu al acestei structuri la sfârșitul secțiunii următoare.

Utilizarea componentelor individuale de fișiere Vue

Am făcut magazinul Vuex cât mai modular posibil. Următorul lucru pe care îl putem face este să aplicăm aceeași strategie și pentru componentele Vue.js. Putem pune fiecare componentă într-un fișier unic, autonom cu o .vue extensie. Pentru a afla cum funcționează acest lucru, puteți vizita pagina de documentare Vue Single File Components. 

Deci, în cazul nostru, vom avea trei fișiere: Parent.vueChildA.vue, și ChildB.vue

În cele din urmă, dacă combinăm toate cele trei tehnici, vom ajunge la următoarea structură sau similară:

├── index.html └── src ├── main.js ├── App.vue ├── componente │ ├── Parent.vue │ ├── ChildA.vue │ ├── ChildB.vue └── ├── store.js ├── state.js ├── getters.js ├── mutations.js ├── actions.js └── module ├── childA.js └── childB.js magazin

În replica GitHub Tutorial, puteți vedea proiectul finalizat cu structura de mai sus.

Recapitulare

Să recapitulăm câteva puncte principale pe care trebuie să le ții minte despre Vuex:

Vuex este o bibliotecă de stat care ne ajută să construim aplicații complexe și de mari dimensiuni. Utilizează un magazin global, centralizat pentru toate componentele dintr-o aplicație. Pentru a rezuma statul, utilizăm getters. Getters sunt cam asemănător cu proprietățile calculate și sunt o soluție ideală când trebuie să filtrați sau să calculați ceva în timpul rulării.

Magazinul Vuex este reactiv, iar componentele nu pot muta direct starea magazinului. Singura modalitate de mutare a stării este prin comiterea de mutații, care sunt tranzacții sincrone. Fiecare mutație ar trebui să efectueze numai o singură acțiune, trebuie să fie cât mai simplu posibil, și este responsabil numai pentru actualizarea o bucată de stat.

Logica asincronă trebuie încapsulată în acțiuni. Fiecare acțiune poate comite una sau mai multe mutații și o mutație poate fi comisă de mai multe acțiuni. Acțiunile pot fi complexe, dar nu schimbă direct statul.

În cele din urmă, modularitatea este cheia pentru mentenabilitate. Pentru a face față complexității și să facă cod modular, folosim principiul „divide și cucerește“ și tehnica de cod divizare.

Concluzie

Asta e! Știți deja principalele concepte din spatele Vuex și sunteți gata să începeți să le aplicați în practică.  

Din motive de concizie și simplitate, am omis în mod intenționat unele detalii și caracteristici ale Vuex, așa că va trebui să citiți documentația completă Vuex pentru a afla totul despre Vuex și setul de funcții.

Cod