Nous voilà déjà au quatrième volet du tutoriel Vue.js 2.0 ! Au menu aujourd’hui : le pattern gestionnaire d’état dans Vue.js et une implémentation officielle avec Vuex.
Les précédents volets sont toujours accessibles :
- Volume 1 : pour créer un projet, créer un composant et transmettre des données d’un composant parent à un composant enfant ;
- Volume 2 : pour apprendre à modifier les données d’un composant et de quelle façon invoquer un service REST ;
- Volume 3 : pour transmettre des données d’un composant enfant à un composant parent et découvrir la communication par événements.
Le code source est quant à lui toujours disponible ici. Enjoy !
Gestionnaire d’état
NB: cette partie décrit les composants se situant dans src/components/chap8 et dans src/pages/chap8
Pattern
Dans les étapes précédentes, nous avons utilisé la communication entre composants par l’intermédiaire d’événements pour transmettre un même objet utilisé dans chacun des composants.
Cependant, ce “pattern” n’est pas toujours pertinent, en particulier dès lors qu’on souhaite partager entre plusieurs composants un objet dont l’état est amené à évoluer. Avec ce “pattern” :
- il faudrait nécessairement ré-émettre un objet mis à jour, faire en sorte qu’il soit capté par tous les composants intéressés,
- potentiellement l’état de l’objet peut être modifié à différents endroits dans l’application,
- l’état le plus récent de l’objet peut être perdu si l’instance du composant qui le porte est détruite,
- etc.
On obtiendrait donc rapidement un plat de spaghettis rendant l’analyse et la maintenance difficiles, avec accessoirement une probable sur-consommation mémoire côté client avec l’accumulation d’instances d’objets inutiles.
Un pattern plus adapté à ce cas de figure est le Gestionnaire d’état dans lequel un objet unique (le store) centralise un état (un ensemble d’attributs) qui sera partagé entre les différents composants d’une application. Les composants en question ne manipulant l’état du store qu’à travers les fonctions dont ce dernier dispose.
Une implémentation très simple de ce pattern est proposée sur le site de Vue.js. Elle consiste à définir un objet store contenant un attribut state regroupant toutes les données constitutives de l’état du store et un ensemble de fonctions permettant de modifier ces données :
Cet objet est ensuite accédé par différents composants de la façon suivante :
Cependant, une implémentation de ce genre n’assure en rien que les développeurs modifieront le store uniquement par l’intermédiaire des fonctions proposées par l’objet : dans l’exemple proposé sur le site de Vue.js il n’existe aucun garde-fou pour empêcher une modification directe de store.state.message ailleurs dans l’application et le “point unique de vérité” qu’est supposé constituer le Gestionnaire d’état n’est donc pas garanti.
Une implémentation plus stricte du pattern Gestionnaire d’état est disponible à travers Vuex, l’implémentation officielle de ce pattern pour Vue.js, l’équivalent de Redux pour React.
Vuex : concepts
A la manière de Redux (et du pattern architectural Flux de Facebook dont il est largement inspiré), Vuex impose un flux de données unidirectionnel, dont le principe est opposé au magique “two-way bindings” de la directive v-model de Vue.js :
Les concepts manipulés sont les suivants :
- le state, l’arbre unique des attributs constitutifs de l’état qui sera partagé entre les composants ;
- les mutations, les seules fonctions par lesquelles on peut passer pour modifier le state et dans lesquelles seules des actions synchrones peuvent être effectuées ;
- les actions, des fonctions qui déclenchent une ou plusieurs mutations et faisant potentiellement des appels complémentaires, en particulier des appels asynchrones.
En complément, les getters sont les fonctions par lesquelles ont peut “lire” le state.
Tous ces éléments sont regroupés dans le store de Vuex, unique pour une application donnée. La possibilité de répartir le state dans plusieurs modules permet de gérer plusieurs états différents dans l’unique store de l’application.
Contrairement à Redux, le state demeure donc “mutable” par l’intermédiaire des mutations et toute modification est automatiquement répercutée par Vue.js sur les éléments des composants nécessitant un nouveau rendu.
Vuex en pratique
Passons à la pratique en commençant par installer les modules nécessaires pour utiliser Vuex via la simple commande npm install vuex --save
Définissons à présent le store de notre application en créant un fichier AppStore.js :
Dans notre application nous avons besoin que le citoyen sélectionné soit partagé entre tous les composants. C’est donc le citoyen sélectionné qui constituera le state. On va modifier le store pour faire en sorte d’ajouter :
- une action permettant d’appeler l’API de détail d’un citoyen sur le citoyen sélectionné (cet appel était précédemment dans Citizen.vue) ;
- une mutation invoquée par l’action précédente pour affecter le résultat obtenu au state (NB : la convention Flux a été conservée, le nom des fonctions de mutation est en majuscules) ;
- un getter pour récupérer les informations du citoyen sélectionné.
Le store devient alors :
Le store défini, il faut à présent le déclarer dans notre application. Pour qu’il puisse être utilisé par tous les composants de l’application, on le déclare dans main.js :
L’import et l’utilisation de Vuex par Vue donnent la possibilité d’injecter une instance de store dans le composant racine en le rendant automatiquement accessible à tous ses composants enfants.
Il ne reste plus qu’à manipuler le store dans les composants Citizens.vue et Citizen.vue.
Si on repart du composant Citizens.vue construit au chapitre précédent, des changements ne sont nécessaires que dans la partie script, simplement en invoquant l’action selectCitizen du store dans la méthode selectCitizen de notre composant au lieu d’émettre un événement :
Plutôt que d’invoquer directement le store pour exécuter une action, il est également possible de rattacher les actions du store au composant via le handler mapActions. De cette façon les actions du store peuvent alors être invoquées dans le composant comme s’il s’agissait de méthodes du composant lui-même :
Cliquez ici pour quelques explications sur l’opérateur de décomposition …
Le code de Citizen.vue, amputé de la réception d’un événement et de son traitement par l’appel au service de détail d’un citoyen désormais à la charge du store, est quant à lui très simplifié puisque sa section script se résume à :
Si on compare cette version à la précédente :
- on constate que le hook created a été supprimé, puisqu’il n’est plus nécessaire de faire réagir le composant à l’émission d’un événement lors de la sélection d’un citoyen de la liste ;
- que les data de la version précédente du composant, constituées de l’objet citizen stockant le citoyen sélectionné, ont été supprimées au profit de l’invocation du getter selectedCitizen du store ; on notera que le fait de rattacher ce getter au composant en l’intitulant citizen (via le handler mapGetters) permet de ne pas toucher au template qui est donc identique à la version précédente du composant ; de plus, positionner ce mapping en computed permet de profiter à nouveau du two-ways-binding puisque dès qu’un nouveau citoyen est sélectionné dans la liste, donc dès que le state du store est modifié, le composant réagit automatiquement en affichant les informations du nouveau citoyen sélectionné.
En fait à ce stade, toujours rien ne garantit l’impossibilité d’accéder directement au state pour le modifier. Par exemple, ajoutons dans le composant Citizen.vue une méthode mettant à jour l’attribut prénom du citoyen sélectionné directement en accédant au state, ainsi qu’un bouton exécutant cette méthode lors d’un clic :
Résultat : le clic sur le bouton “Do update” modifie bien la valeur du prénom du citoyen dans le store.
Vuex permet d’alerter lorsqu’une modification directe du store est effectuée dans le code en activant simplement le paramètre strict dans la définition du store :
Avec ce paramétrage activé, le message suivant apparaît dans la console du navigateur lorsqu’on clique sur le bouton “Do update” :
Attention : le mode strict doit être positionné à false en production pour éviter les problèmes de performances.
A suivre…
Avec ce quatrième article de notre série, vous savez désormais comment partager des données entre vos composants sans utiliser exagérément une communication événementielle qui risquerait de rendre rapidement vos applications, même de taille raisonnable, difficiles à maintenir. Nous avons encore de nombreux sujets à aborder concernant Vue.js 2.0 et parmi eux je vous proposerai d’ici peu d’étudier de quelle façon faire du routage. A très bientôt !