Comment créer des applications Vue.js complexes et à grande échelle avec Vuex

Il est si facile d'apprendre et d'utiliser Vue.js que tout le monde peut créer une application simple avec ce framework. Même les novices, à l'aide de la documentation de Vue, peuvent faire le travail. Cependant, lorsque la complexité entre en jeu, les choses deviennent un peu plus sérieuses. La vérité est que plusieurs composants profondément imbriqués avec un état partagé peuvent rapidement transformer votre application en désordre immuable..

Le problème principal dans une application complexe est de savoir comment gérer l’état entre les composants sans écrire de code spaghetti ni produire d’effets secondaires. Dans ce tutoriel, vous allez apprendre à résoudre ce problème en utilisant Vuex: une bibliothèque de gestion d'état pour la construction d'applications Vue.js complexes..

Qu'est-ce que Vuex??

Vuex est une bibliothèque de gestion d'état spécialement conçue pour la création d'applications Vue.js complexes et à grande échelle. Il utilise un magasin global centralisé pour tous les composants d'une application et tire parti de son système de réactivité pour des mises à jour instantanées..

Le magasin Vuex est conçu de manière à ce qu'il ne soit pas possible de changer son état à partir d'un composant. Cela garantit que l'état ne peut être muté que de manière prévisible. Ainsi, votre magasin devient une source unique de vérité: chaque élément de données n'est stocké qu'une seule fois et est en lecture seule pour empêcher les composants de l'application de corrompre l'état auquel accèdent d'autres composants..

Pourquoi avez-vous besoin de Vuex?

Vous pouvez demander: Pourquoi ai-je besoin de Vuex en premier lieu? Je ne peux pas simplement mettre l'état partagé dans un fichier JavaScript normal et l'importer dans mon application Vue.js?

Vous pouvez bien sûr, mais comparé à un objet global simple, le magasin Vuex présente des avantages et des avantages non négligeables:

  • Le magasin Vuex est réactif. Une fois que les composants ont récupéré un état, ils mettent à jour leurs vues de manière réactive à chaque changement d'état..
  • Les composants ne peuvent pas directement muter l'état du magasin. La seule façon de modifier l'état du magasin consiste à commettre explicitement des mutations. Cela garantit que chaque changement d'état laisse un enregistrement traçable, ce qui facilite l'application pour déboguer et tester.
  • Vous pouvez facilement déboguer votre application grâce à l'intégration de Vuex avec l'extension DevTools de Vue.
  • Le magasin Vuex vous donne une vue d'ensemble de la manière dont tout est connecté et affecté dans votre application.
  • Il est plus facile de maintenir et de synchroniser l'état entre plusieurs composants, même si la hiérarchie des composants change.
  • Vuex permet une communication directe entre composants.
  • Si un composant est détruit, l'état dans le magasin Vuex reste intact..

Débuter avec Vuex

Avant de commencer, je tiens à préciser plusieurs choses. 

Premièrement, pour suivre ce tutoriel, vous devez avoir une bonne compréhension de Vue.js et de son système de composants, ou au moins une expérience minimale du framework.. 

En outre, l'objectif de ce didacticiel n'est pas de vous montrer comment créer une application complexe réelle. L’objectif est de vous concentrer davantage sur les concepts Vuex et sur la manière de les utiliser pour créer des applications complexes. Pour cette raison, je vais utiliser des exemples très simples et simples, sans code redondant. Une fois que vous aurez compris les concepts de Vuex, vous pourrez les appliquer à n’importe quel niveau de complexité..

Enfin, je vais utiliser la syntaxe ES2015. Si vous ne le connaissez pas, vous pouvez l'apprendre ici.

Et maintenant, commençons!

Configurer un projet Vuex

Pour commencer à utiliser Vuex, la première étape consiste à installer Vue.js et Vuex sur votre ordinateur. Il y a plusieurs façons de le faire, mais nous utiliserons la plus simple. Créez simplement un fichier HTML et ajoutez les liens CDN nécessaires:

            

J'ai utilisé du CSS pour rendre les composants plus jolis, mais vous n'avez pas à vous soucier de ce code CSS. Cela vous aide seulement à avoir une idée visuelle de ce qui se passe. Il suffit de copier et coller ce qui suit à l’intérieur du  étiquette:

Maintenant, créons des composants avec lesquels travailler. À l'intérieur de > tag, juste au dessus de la fermeture  tag, mettez le code Vue suivant:

Vue.component ('ChildB', template: ' 

But:

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

But:

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

But:

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

Ici, nous avons une instance Vue, un composant parent et deux composants enfants. Chaque composant a un en-tête "But:"où nous sortirons l'état de l'application.

La dernière chose que vous devez faire est de mettre un emballage

 avec id = "app" juste après l'ouverture , puis placez le composant parent à l'intérieur:

Le travail de préparation est terminé et nous sommes prêts à passer à autre chose.

Explorer Vuex

Gestion d'état

Dans la vie réelle, nous gérons la complexité en utilisant des stratégies pour organiser et structurer le contenu que nous voulons utiliser. Nous regroupons les éléments liés dans différentes sections, catégories, etc. C'est comme une bibliothèque de livres, dans laquelle les livres sont classés et classés en différentes sections de manière à ce que nous puissions facilement trouver ce que nous recherchons. Vuex organise les données d'application et la logique associées à l'état en quatre groupes ou catégories: état, accesseurs, mutations et actions..

L'état et les mutations constituent la base de tout magasin Vuex:

  • Etat est un objet qui contient l'état des données de l'application.
  • mutations est aussi un objet contenant des méthodes qui affectent l'état.

Les accesseurs et les actions ressemblent à des projections logiques d'état et de mutations:

  • Getters contient les méthodes utilisées pour résumer l'accès à l'état et pour effectuer quelques tâches de prétraitement, si nécessaire (calcul des données, filtrage, etc.).
  • actes sont des méthodes utilisées pour déclencher des mutations et exécuter du code asynchrone.

Explorons le diagramme suivant pour clarifier les choses:

Sur le côté gauche, nous avons un exemple de magasin Vuex, que nous créerons plus tard dans ce tutoriel. Sur le côté droit, nous avons un diagramme de flux de travail Vuex, qui montre comment les différents éléments Vuex fonctionnent ensemble et communiquent entre eux..

Pour changer l’état, un composant Vue particulier doit valider des mutations (par exemple,. ceci. $ store.commit ('increment', 3)), puis ces mutations changent l’état (But devient 3). Ensuite, les getters sont automatiquement mis à jour grâce au système réactif de Vue et rendent les mises à jour dans la vue du composant (avec cela. $ store.getters.score). 

Les mutations ne peuvent pas exécuter de code asynchrone, car cela rendrait impossible l'enregistrement et le suivi des modifications dans des outils de débogage tels que Vue DevTools. Pour utiliser la logique asynchrone, vous devez la mettre en actions. Dans ce cas, un composant va d’abord envoyer des actions (ceci. $ store.dispatch ('incrementScore', 3000)) où le code asynchrone est exécuté, puis ces actions valideront des mutations, ce qui mutera l'état. 

Créer un squelette de magasin Vuex

Maintenant que nous avons exploré le fonctionnement de Vuex, créons le squelette de notre magasin Vuex. Mettez le code suivant au dessus de la ChildB enregistrement du composant:

const store = new Vuex.Store (state: , getters: , mutations: , actions: )

Pour fournir un accès global au magasin Vuex à partir de chaque composant, nous devons ajouter le le magasin propriété dans l'occurrence Vue:

new Vue (el: '#app', store // inscrire le magasin Vuex de manière globale)

Maintenant, nous pouvons accéder au magasin à partir de chaque composant avec le ce magasin. $ variable.

Jusqu'ici, si vous ouvrez le projet avec CodePen dans le navigateur, vous devriez voir le résultat suivant.

Propriétés d'état

L'objet state contient toutes les données partagées dans votre application. Bien sûr, si nécessaire, chaque composant peut avoir son propre état privé.

Imaginez que vous souhaitiez créer une application de jeu et que vous ayez besoin d'une variable pour stocker le score du jeu. Donc, vous le mettez dans l'objet d'état:

état: score: 0

Maintenant, vous pouvez accéder directement au score de l'état. Revenons aux composants et réutilisons les données du magasin. Pour pouvoir réutiliser les données réactives de l'état du magasin, vous devez utiliser les propriétés calculées. Alors créons un But() Propriété calculée dans le composant parent:

calculé: score () retourne ceci. $ store.state.score

Dans le modèle du composant parent, mettez le But expression:

Score: score

Et maintenant, faites la même chose pour les deux composants enfants.

Vuex est tellement intelligent qu'il fera tout le travail pour mettre à jour de manière réactive le But propriété chaque fois que l'état change. Essayez de changer la valeur du score et de voir comment le résultat est mis à jour dans les trois composants..

Créer des Getters

Bien sûr, il est bon que vous puissiez réutiliser le cette. $ store.state mot-clé à l'intérieur des composants, comme vous l'avez vu ci-dessus. Mais imaginons les scénarios suivants:

  1. Dans une application à grande échelle, où plusieurs composants accèdent à l’état du magasin en utilisant cela. $ store.state.score, vous décidez de changer le nom de But. Cela signifie que vous devez changer le nom de la variable à l'intérieur de chaque composant qui l'utilise! 
  2. Vous voulez utiliser une valeur calculée de l'état. Par exemple, supposons que vous souhaitiez donner aux joueurs un bonus de 10 points lorsque le score atteint 100 points. Ainsi, lorsque le score atteint 100 points, un bonus de 10 points est ajouté. Cela signifie que chaque composant doit contenir une fonction qui réutilise le score et l'incrémente de 10. Vous aurez du code répété dans chaque composant, ce qui n'est pas bon du tout.!

Heureusement, Vuex offre une solution efficace pour faire face à de telles situations. Imaginez le getter centralisé qui accède à l'état du magasin et fournit une fonction de getter à chacun des éléments de l'état. Si nécessaire, ce getter peut appliquer des calculs à l'article de l'état. Et si vous avez besoin de changer les noms de certaines propriétés de l'état, vous ne les changez qu'à un seul endroit, dans ce getter.. 

Créons un But() getter:

getters: score (état) retour state.score

Un getter reçoit le Etat comme premier argument, puis l'utilise pour accéder aux propriétés de l'état.

Note: Les Getters reçoivent aussi Getters comme deuxième argument. Vous pouvez l'utiliser pour accéder aux autres accesseurs du magasin..

Dans tous les composants, modifiez le But() propriété calculée pour utiliser le But() getter au lieu du score de l'état directement.

calculé: score () retourne ceci. $ store.getters.score

Maintenant, si vous décidez de changer le But à résultat, vous devez le mettre à jour uniquement à un endroit: dans le But() getter. Essayez-le dans ce CodePen!

Créer des mutations

Les mutations sont le seul moyen autorisé de changer d'état. Déclencher des changements signifie simplement commettre des mutations dans des méthodes de composant.

Une mutation est en gros une fonction de gestionnaire d'événements définie par son nom. Les fonctions du gestionnaire de mutations reçoivent un Etat comme premier argument. Vous pouvez également passer un deuxième argument supplémentaire, appelé le charge utile pour la mutation. 

Créons un incrément() mutation:

mutations: increment (state, step) state.score + = step

Les mutations ne peuvent pas être appelées directement! Pour effectuer une mutation, vous devez appeler le commettre() méthode avec le nom de la mutation correspondante et les paramètres supplémentaires possibles. Ce pourrait être juste un, comme le étape dans notre cas, ou il pourrait y en avoir plusieurs enveloppés dans un objet.

Utilisons le incrément() mutation dans les deux composants enfants en créant une méthode nommée changeScore ():

méthodes: changeScore () this. $ store.commit ('increment', 3); 

Nous commettons une mutation au lieu de changer cela. $ store.state.score directement, car nous voulons suivre explicitement le changement apporté par la mutation. De cette manière, nous rendons notre logique d’application plus transparente, traçable et facile à raisonner. En outre, il permet d’implémenter des outils, tels que Vue DevTools ou Vuetron, qui permettent de consigner toutes les mutations, de prendre des instantanés d’état et de procéder au débogage de la course dans le temps..

Maintenant, mettons le changeScore () méthode en cours d'utilisation. Dans chaque modèle des deux composants enfants, créez un bouton et ajoutez-lui un écouteur d'événement de clic:

Lorsque vous cliquez sur le bouton, l'état sera incrémenté de 3 et cette modification sera reflétée dans tous les composants. Nous avons maintenant atteint la communication directe inter-composants, ce qui n’est pas possible avec le mécanisme intégré de Vue.js, "props down down, events up". Découvrez-le dans notre exemple CodePen.

Création d'actions

Une action est juste une fonction qui commet une mutation. Cela change indirectement l'état, ce qui permet l'exécution d'opérations asynchrones. 

Créons un incrementScore () action:

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

Les actions obtiennent le le contexte en tant que premier paramètre, qui contient toutes les méthodes et propriétés du magasin. Habituellement, nous extrayons simplement les pièces dont nous avons besoin en utilisant la déstructuration des arguments ES2015. le commettre La méthode est celle dont nous avons besoin très souvent. Les actions obtiennent également un deuxième argument de charge utile, tout comme les mutations.

dans le ChildB composant, modifiez le changeScore () méthode:

méthodes: changeScore () this. $ store.dispatch ('incrementScore', 3000); 

Pour appeler une action, nous utilisons le envoi() méthode avec le nom de l'action correspondante et des paramètres supplémentaires, comme pour les mutations.

Maintenant le Changer le score bouton du ChildA composant incrémentera le score de 3. Le bouton identique à celui du ChildB composant fera de même, mais après un délai de 3 secondes. Dans le premier cas, nous exécutons du code synchrone et nous utilisons une mutation, mais dans le second cas, nous exécutons du code asynchrone et nous devons utiliser une action à la place. Voyez comment tout cela fonctionne dans notre exemple CodePen.

Assistants de cartographie Vuex

Vuex propose des aides utiles qui peuvent simplifier le processus de création d’états, de getters, de mutations et d’actions. Au lieu d'écrire ces fonctions manuellement, nous pouvons dire à Vuex de les créer pour nous. Voyons voir comment ça fonctionne.

Au lieu d'écrire le But() propriété calculée comme ceci:

calculé: score () retourne ceci. $ store.state.score

Nous utilisons simplement le mapState () assistant comme ceci:

calculé: … Vuex.mapState (['score']])

Et le But() la propriété est créée automatiquement pour nous.

La même chose est vraie pour les accesseurs, les mutations et les actions. 

Pour créer le But() getter, on utilise le mapGetters () assistant:

calculé: … Vuex.mapGetters (['score'])

Pour créer le changeScore () méthode, nous utilisons le mapMutations () assistant comme ceci:

méthodes: … Vuex.mapMutations (changeScore: 'increment')

Lorsqu'il est utilisé pour des mutations et des actions avec l'argument payload, nous devons passer cet argument dans le modèle où nous définissons le gestionnaire d'événements:

Si nous voulons changeScore () d'utiliser une action au lieu d'une mutation, on utilise mapActions () comme ça:

méthodes: … Vuex.mapActions (changeScore: 'incrementScore')

De nouveau, nous devons définir le délai dans le gestionnaire d’événements:

Remarque: Tous les assistants de mappage renvoient un objet. Donc, si nous voulons les utiliser en combinaison avec d’autres méthodes ou propriétés calculées locales, nous devons les fusionner en un seul objet. Heureusement, avec l'opérateur de propagation d'objet (), nous pouvons le faire sans utiliser aucun utilitaire. 

Dans notre CodePen, vous pouvez voir un exemple de la manière dont tous les aides de cartographie sont utilisés dans la pratique..

Rendre le magasin plus modulaire

Il semble que le problème de la complexité nous gêne constamment. Nous l'avons déjà résolu en créant le magasin Vuex, où nous avons facilité la gestion des états et la communication des composants. Dans ce magasin, nous avons tout au même endroit, facile à manipuler et à raisonner à propos de. 

Cependant, au fur et à mesure que notre application se développe, ce fichier de magasin facile à gérer devient de plus en plus grand et, par conséquent, plus difficile à gérer. Encore une fois, nous avons besoin de stratégies et de techniques pour améliorer la structure d’application en la rendant sous une forme facile à gérer. Dans cette section, nous allons explorer plusieurs techniques qui peuvent nous aider dans cette entreprise..

Utiliser les modules Vuex

Vuex nous permet de scinder l'objet store en modules distincts. Chaque module peut contenir son propre état, ses mutations, ses actions, ses getters et autres modules imbriqués. Après avoir créé les modules nécessaires, nous les enregistrons dans le magasin..

Voyons le en action:

const childB = state: result: 3, getters: result (state) return state.result, mutations: augmentation (état, étape) state.result + = step, actions: augmentationRésultat : (commit, delay) => setTimeout (() => commit ('augmentation', 6), delay) const childA = état: score: 0, getters: score ( state) return state.score, mutations: increment (état, étape) state.score + = step, actions: incrementScore: (commit, delay) => setTimeout (() => commit ('increment', 3), delay) const store = new Vuex.Store (modules: scoreBoard: childA, resultBoard: childB)

Dans l'exemple ci-dessus, nous avons créé deux modules, un pour chaque composant enfant. Les modules ne sont que des objets simples, que nous enregistrons comme scoreBoard et résultatBoard dans le modules objet à l'intérieur du magasin. Le code pour enfantA est le même que celui dans le magasin des exemples précédents. Dans le code pour enfantB, nous ajoutons quelques changements dans les valeurs et les noms.

Réglons maintenant le ChildB composant pour refléter les changements dans la résultatBoard module. 

Vue.component ('ChildB', template: ' 

Résultat: résultat

', calculé: result () return this. $ store.getters.result, méthodes: changeResult () this. $ store.dispatch (' augmentationResult ', 3000); )

dans le ChildA composant, la seule chose que nous devons modifier est la changeScore () méthode:

Vue.component ('ChildA', template: ' 

Score: score

', calculé: score () retourne ceci. $ store.getters.score, méthodes: changeScore () ceci. $ store.dispatch (' incrementScore ', 3000); )

Comme vous pouvez le constater, le fait de scinder le magasin en modules le rend beaucoup plus léger et facile à gérer, tout en conservant ses fonctionnalités. Découvrez le CodePen mis à jour pour le voir en action.

Namespaced Modules

Si vous voulez ou devez utiliser un seul et même nom pour une propriété ou une méthode particulière dans vos modules, vous devez alors envisager de leur attribuer un nom. Sinon, vous pourriez observer des effets secondaires étranges, tels que l'exécution de toutes les actions portant le même nom ou l'obtention des valeurs du mauvais état. 

Pour nommer un module Vuex, il vous suffit de définir la nompace propriété à vrai.

const childB = namespaced: true, state: score: 3, getters: score (state) return state.score, mutations: increment (state, step) state.score + = step, actions: incrementScore: (commit, delay) => setTimeout (() => commit ('increment', 6), delay) const enfantA = namespaced: true, state: score: 0, getters: score (state) return state.score, mutations: increment (state, step) state.score + = step, actions: incrementScore: (commit, delay) = > setTimeout (() => commit ('increment', 3), délai)

Dans l'exemple ci-dessus, les noms de propriété et de méthode sont identiques pour les deux modules. Et maintenant, nous pouvons utiliser une propriété ou une méthode préfixée du nom du module. Par exemple, si nous voulons utiliser le But() getter du résultatBoard module, nous le tapons comme ceci: résultatBoard / score. Si on veut le But() getter du scoreBoard module, alors nous le tapons comme ceci: scoreBoard / score

Modifions maintenant nos composants pour refléter les modifications apportées. 

Vue.component ('ChildB', template: ' 

Résultat: résultat

', calculé: result () return this. $ store.getters [' resultBoard / score '], méthodes: changeResult () this. $ store.dispatch (' resultBoard / incrementScore ', 3000); ) Vue.component ('ChildA', template: '

Score: score

', calculé: score () retourne ceci. $ store.getters [' scoreBoard / score '], méthodes: changeScore () ceci. $ store.dispatch (' scoreBoard / incrementScore ', 3000); )

Comme vous pouvez le constater dans notre exemple CodePen, nous pouvons maintenant utiliser la méthode ou la propriété souhaitée et obtenir le résultat escompté..

Fractionner le magasin Vuex en fichiers séparés

Dans la section précédente, nous avons amélioré dans une certaine mesure la structure de l'application en séparant le magasin en modules. Nous avons rendu le magasin plus propre et plus organisé, mais tout le code du magasin et ses modules se trouvent dans le même gros fichier. 

La prochaine étape logique consiste donc à scinder le magasin Vuex en fichiers séparés. L'idée est d'avoir un fichier individuel pour le magasin et un pour chacun de ses objets, modules compris. Cela signifie avoir des fichiers séparés pour l’état, les getters, les mutations, les actions et pour chaque module individuel (store.jsstate.js, getters.js, etc.) Vous pouvez voir un exemple de cette structure à la fin de la section suivante.

Utilisation des composants Vue Single File

Nous avons rendu le magasin Vuex aussi modulaire que possible. Nous pouvons ensuite appliquer la même stratégie aux composants Vue.js. Nous pouvons mettre chaque composant dans un seul fichier autonome avec un .vue extension. Pour savoir comment cela fonctionne, visitez la page de documentation de Vue Single File Components. 

Donc, dans notre cas, nous aurons trois fichiers: Parent.vueChildA.vue, et ChildB.vue

Enfin, si nous combinons les trois techniques, nous obtiendrons la structure suivante ou similaire:

Index.html └── src ├── main.js ├── Composants App.vue Parent.vue ChildA.vue ChildB.vue store ├── store.js ├── state.js getters.js mutations.js actions.js └── modules ├── childA.js childB.js

Dans notre tutoriel GitHub repo, vous pouvez voir le projet terminé avec la structure ci-dessus.

résumer

Récapitulons quelques points importants à connaître à propos de Vuex:

Vuex est une bibliothèque de gestion d'état qui nous aide à construire des applications complexes à grande échelle. Il utilise un magasin global centralisé pour tous les composants d'une application. Pour résumer l'état, nous utilisons des accesseurs. Les accesseurs ressemblent beaucoup aux propriétés calculées et constituent une solution idéale lorsque vous devez filtrer ou calculer quelque chose au moment de l'exécution..

Le magasin Vuex est réactif et les composants ne peuvent pas directement modifier l'état du magasin. Le seul moyen de muter l'État est de commettre des mutations, qui sont des transactions synchrones. Chaque mutation ne doit effectuer qu’une seule action, doit être aussi simple que possible et n’est responsable que de la mise à jour d’un élément de l’état..

La logique asynchrone doit être encapsulée dans des actions. Chaque action peut commettre une ou plusieurs mutations et une mutation peut être commise par plus d'une action. Les actions peuvent être complexes, mais elles ne changent jamais l'état directement.

Enfin, la modularité est la clé de la maintenabilité. Pour traiter la complexité et rendre notre code modulaire, nous utilisons le principe de "diviser pour régner" et la technique de division de code..

Conclusion

C'est tout! Vous connaissez déjà les concepts principaux de Vuex et vous êtes prêt à les appliquer dans la pratique..  

Par souci de brièveté et de simplicité, j'ai volontairement omis certains détails et fonctionnalités de Vuex. Vous devez donc lire la documentation complète de Vuex pour tout savoir sur Vuex et ses fonctionnalités..