Comment organiser vos dépendances dans une application Vue

Tous ceux qui connaissent Vue savent qu'une application Vue a un point d'entrée - le fichier main.js Là, en plus de créer une instance de Vue, il y a une importation et une sorte d'injection de dépendances de toutes vos dépendances globales (directives, composants, plugins). Plus le projet est volumineux, plus les dépendances deviennent importantes et, de plus, chacune a sa propre configuration. En conséquence, nous obtenons un énorme fichier avec toutes les configurations.
Cet article explique comment organiser les dépendances globales pour éviter cela.



Pourquoi l'écrire vous-même?


Beaucoup peuvent penser - pourquoi est-ce nécessaire s'il y a, par exemple, Nuxt , qui fera cela pour vous? Dans mes projets, je l'ai également utilisé, mais dans les projets simples, cela peut être redondant. De plus, personne n'a annulé des projets avec du code hérité qui vous tombent dessus comme de la neige sur la tête. Et connectez le cadre là-bas - faites-le pratiquement à partir de zéro.

Mastermind


L'organisateur d'une telle organisation était Nuxt. Il a été utilisé par moi sur un grand projet avec Vue.
Nuxt a une grande fonctionnalité - plugins. Chaque plugin est un fichier qui exporte une fonction. La configuration est transmise à la fonction, qui sera également transmise au constructeur Vue lors de la création de l'instance, ainsi qu'au magasin entier.

De plus, une fonctionnalité extrêmement utile, inject est disponible dans chaque plugin. Il effectue une injection de dépendance à l'instance racine de Vue et à l'objet de store . Et cela signifie que dans chaque composant, dans chaque fonction de stockage, la dépendance spécifiée sera disponible par this biais.

Où cela peut-il être utile?


En plus du fait que main.js "perd du poids" de main.js significative, vous aurez également la possibilité d'utiliser la dépendance n'importe où dans l'application sans importations inutiles.

Vue-router est un excellent exemple d'injection de dépendance. Il n'est pas utilisé très souvent - pour obtenir les paramètres de l'itinéraire actuel, pour faire une redirection, mais c'est une dépendance globale. S'il peut être utile dans n'importe quel composant, alors pourquoi ne pas le rendre mondial? De plus, grâce à cela, son état sera également stocké globalement et modifié pour l'ensemble de l'application.

Un autre exemple est la vue-wait . Les développeurs de ce plugin sont allés plus loin et ont ajouté la propriété $wait non seulement à l'instance Vue, mais aussi à la boutique vuex. Compte tenu des spécificités du plugin, cela s'avère extrêmement utile. Par exemple, le magasin a une action qui est appelée sur plusieurs composants. Et dans chaque cas, vous devez montrer le chargeur sur un élément. Au lieu d'appeler $wait.start('action') et $wait.end('action') avant et après chaque appel d'action, vous pouvez simplement appeler ces méthodes une fois dans l'action elle-même. Et c'est beaucoup plus lisible et moins verbeux que dispatch('wait/start', 'action' {root: true}) . Dans le cas du magasin, il s'agit de sucre syntaxique.

Des mots au code


La structure de base du projet


Voyons maintenant à quoi ressemble le projet:
src
- store
- App.vue
- main.js

main.js ressemble à ceci:
 import Vue from 'vue'; import App from './App.vue'; import store from './store'; new Vue({ render: h => h(App), store }).$mount('#app'); 


Nous connectons la première dépendance


Maintenant, nous voulons connecter axios à notre projet et créer une sorte de configuration pour celui-ci. J'ai suivi la terminologie Nuxt et créé un répertoire de plugins dans src . Le répertoire axios.js index.js et axios.js .

src
- plugins
-- index.js
-- axios.js
- store
- App.vue
- main.js

Comme mentionné ci-dessus, chaque plugin doit exporter une fonction. Dans le même temps, à l'intérieur de la fonction, nous voulons avoir accès au magasin et par la suite à la fonction d' inject .

axios.js
 import axios from 'axios'; export default function (app) { //       – , , interceptors  .. axios.defaults.baseURL = process.env.API_BASE_URL; axios.defaults.headers.common['Accept'] = 'application/json'; axios.defaults.headers.post['Content-Type'] = 'application/json'; axios.interceptors.request.use(config => { ... return config; }); } 

index.js :
 import Vue from 'vue'; import axios from './axios'; export default function (app) { let inject = () => {}; //   inject,        Dependency Injection axios(app, inject); //       Vue    } 


Comme vous pouvez le voir, le fichier index.js exporte également la fonction. Ceci est fait afin de pouvoir y passer l'objet app . Maintenant, main.js et appelons cette fonction.

main.js :
 import Vue from 'vue'; import App from './App.vue'; import store from './store'; import initPlugins from './plugins'; //    // ,    Vue,  ,     initPlugins const app = { render: h => h(App), store }; initPlugins(app); new Vue(app).$mount('#app'); //   initPlugins    


Résultat


À ce stade, nous avons réalisé que nous avons supprimé la configuration du plugin de main.js dans un fichier séparé.

Soit dit en passant, l'avantage de transmettre l'objet d' app à tous nos plugins est qu'à l'intérieur de chaque plugin, nous avons maintenant accès au magasin. Vous pouvez l'utiliser librement en appelant commit , dispatch , ainsi qu'en accédant à store.state et store.getters .

Si vous aimez le style ES6, vous pouvez même le faire:

axios.js
 import axios from 'axios'; export default function ({store: {dispatch, commit, state, getters}}) { ... } 

Deuxième étape - Injection de dépendance


Nous avons déjà créé le premier plugin et maintenant notre projet ressemble à ceci:

src
- plugins
-- index.js
-- axios.js
- store
- App.vue
- main.js

Étant donné que dans la plupart des bibliothèques où cela est vraiment nécessaire, l'injection de dépendance est déjà implémentée à l'aide de Vue.use , nous allons créer notre propre plugin simple.

Par exemple, essayez de répéter ce que fait vue-wait . C'est une bibliothèque assez lourde, donc si vous voulez montrer le chargeur sur une paire de boutons, il vaut mieux l'abandonner. Cependant, je n'ai pas pu résister à sa commodité et répété dans son projet sa fonctionnalité de base, y compris le sucre syntaxique dans le magasin.

Plugin d'attente


Créez un autre fichier dans le répertoire des plugins - wait.js

J'ai déjà un module vuex, que j'ai aussi appelé wait . Il fait trois étapes simples:

- start - définit la propriété state d'un objet nommé action sur true
- end - supprime de l'état une propriété d'un objet nommé action
- is - obtient de l'état une propriété d'un objet nommé action

Dans ce plugin, nous allons l'utiliser.

wait.js
 export default function ({store: {dispatch, getters}}, inject) { const wait = { start: action => dispatch('wait/start', action), end: action => dispatch('wait/end', action), is: action => getters['wait/waiting'](action) }; inject('wait', wait); } 


Et connectez notre plugin:

index.js :
 import Vue from 'vue'; import axios from './axios'; import wait from './wait'; export default function (app) { let inject = () => {}; Injection axios(app, inject); wait(app, inject); } 


Fonction d'injection


Maintenant, nous implémentons la fonction inject .
 //   2 : // name – ,       this.  ,   Vue        Dependency Injection // plugin – ,       this.  ,  ,           let inject = (name, plugin) => { let key = `$${name}`; //      app[key] = plugin; //     app app.store[key] = plugin; //     store //  Vue.prototype Vue.use(() => { if (Vue.prototype.hasOwnProperty(key)) { return; } Object.defineProperty(Vue.prototype, key, { get () { return this.$root.$options[key]; } }); }); }; 


La magie de Vue.prototype


Maintenant sur la magie. La documentation Vue indique qu'il suffit d'écrire Vue.prototype.$appName = ' '; et $appName seront disponibles this .

Cependant, en réalité, il s'est avéré que ce n'est pas le cas. En raison de la recherche sur Google, il n'y avait aucune réponse pourquoi un tel design ne fonctionnait pas. J'ai donc décidé de contacter les auteurs du plugin qui l'ont déjà implémenté.

Mixin mondial


Comme dans notre exemple, j'ai regardé le code du plugin vue-wait . Ils offrent une telle implémentation (le code source est nettoyé pour plus de clarté):

 Vue.mixin({ beforeCreate() { const { wait, store } = this.$options; let instance = null; instance.init(Vue, store); // inject to store this.$wait = instance; // inject to app } }); 

Au lieu d'un prototype, il est proposé d'utiliser un mixin global. L'effet est fondamentalement le même, peut-être, à l'exception de certaines nuances. Mais étant donné que l'injection est effectuée dans le magasin ici, elle ne semble pas exactement dans le bon sens et ne correspond pas du tout à la documentation.

Et si c'était un prototype?


L'idée derrière la solution prototype utilisée dans le inject fonction d' inject été empruntée à Nuxt. Il semble beaucoup plus juste que le mixin mondial, alors je me suis installé.

  Vue.use(() => { // ,        if (Vue.prototype.hasOwnProperty(key)) { return; } //    ,         app  Object.defineProperty(Vue.prototype, key, { get () { return this.$root.$options[key]; //  ,    this } }); }); 


Résultat


Après ces manipulations, nous avons l'opportunité d'y accéder this.$wait de n'importe quel composant, ainsi que de n'importe quelle méthode du magasin.

Qu'est-il arrivé?


Structure du projet:

src
- plugins
-- index.js
-- axios.js
-- wait.js
- store
- App.vue
- main.js


index.js :
 import Vue from 'vue'; import axios from './axios'; import wait from './wait'; export default function (app) { let inject = (name, plugin) => { let key = `$${name}`; app[key] = plugin; app.store[key] = plugin; Vue.use(() => { if (Vue.prototype.hasOwnProperty(key)) { return; } Object.defineProperty(Vue.prototype, key, { get () { return this.$root.$options[key]; } }); }); }; axios(app, inject); wait(app, inject); } 


wait.js
 export default function ({store: {dispatch, getters}}, inject) { const wait = { start: action => dispatch('wait/start', action), end: action => dispatch('wait/end', action), is: action => getters['wait/waiting'](action) }; inject('wait', wait); } 


axios.js
 import axios from 'axios'; export default function (app) { axios.defaults.baseURL = process.env.API_BASE_URL; axios.defaults.headers.common['Accept'] = 'application/json'; axios.defaults.headers.post['Content-Type'] = 'application/json'; } 


main.js :
 import Vue from 'vue'; import App from './App.vue'; import store from './store'; import initPlugins from './plugins'; const app = { render: h => h(App), store }; initPlugins(app); new Vue(app).$mount('#app'); 

Conclusion


À la suite des manipulations, nous avons reçu une importation et un appel de fonction dans le fichier main.js Et maintenant, il est immédiatement clair où chercher la configuration pour chaque plugin et chaque dépendance globale.

Lors de l'ajout d'un nouveau plugin, il vous suffit de créer un fichier qui exporte la fonction, de l'importer dans index.js et d'appeler cette fonction.

Dans ma pratique, une telle structure s'est avérée très pratique, de plus, elle est facilement transférable d'un projet à l'autre. Maintenant, il n'y a plus de problème si vous devez faire l'injection de dépendances ou configurer un autre plugin.

Partagez votre expérience avec la gestion des dépendances dans les commentaires. Projets réussis!

Source: https://habr.com/ru/post/fr423013/


All Articles