Como organizar suas dependências em um aplicativo Vue

Todo mundo familiarizado com o Vue sabe que um aplicativo Vue tem um ponto de entrada - o arquivo main.js Lá, além de criar uma instância do Vue, há uma importação e um tipo de Injeção de Dependências de todas as suas dependências globais (diretivas, componentes, plugins). Quanto maior o projeto, mais dependências se tornam, e cada uma delas tem sua própria configuração. Como resultado, obtemos um arquivo enorme com todas as configurações.
Este artigo discutirá como organizar dependências globais para evitar isso.



Por que escrever você mesmo?


Muitos podem pensar - por que isso é necessário se existe, por exemplo, o Nuxt , que fará isso por você? Nos meus projetos, eu também o usei, mas em projetos simples isso pode ser redundante. Além disso, ninguém cancelou projetos com código legado que caem sobre você como neve em sua cabeça. E conecte a estrutura lá - praticamente faça isso do zero.

Mastermind


O organizador dessa organização foi Nuxt. Foi usado por mim em um grande projeto com o Vue.
O Nuxt tem um ótimo recurso - plugins. Cada plug-in é um arquivo que exporta uma função. A configuração é passada para a função, que também será passada para o construtor Vue ao criar a instância, bem como para toda a loja .

Além disso, um recurso extremamente útil, inject está disponível em cada plug-in. Faz uma injeção de dependência na instância raiz do Vue e no objeto de store . E isso significa que em cada componente, em cada função de armazenamento, a dependência especificada estará disponível por meio this .

Onde isso pode ser útil?


Além do fato de o main.js "perder peso significativamente", você também terá a oportunidade de usar a dependência em qualquer lugar do aplicativo, sem importações desnecessárias.

Um excelente exemplo de injeção de dependência é o vue-router . Não é usado com muita frequência - para obter os parâmetros da rota atual, fazer um redirecionamento, mas essa é uma dependência global. Se ele pode ser útil em qualquer componente, por que não torná-lo global? Além disso, graças a isso, seu estado também será armazenado globalmente e alterado para todo o aplicativo.

Outro exemplo é vue-wait . Os desenvolvedores deste plug-in foram além e adicionaram a propriedade $wait não apenas à instância do Vue, mas também à loja do vuex. Dadas as especificidades do plug-in, isso se mostra extremamente útil. Por exemplo, a loja possui uma ação chamada em vários componentes. E em cada caso, você precisa mostrar o carregador em algum elemento. Em vez de chamar $wait.start('action') e $wait.end('action') antes e depois de cada chamada de ação, você pode simplesmente chamar esses métodos uma vez na própria ação. E isso é muito mais legível e menos detalhado que o dispatch('wait/start', 'action' {root: true}) . No caso da loja, isso é açúcar sintático.

Das palavras ao código


A estrutura básica do projeto


Vamos ver como é o projeto agora:
src
- store
- App.vue
- main.js

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


Nós conectamos a primeira dependência


Agora queremos conectar axios ao nosso projeto e criar algum tipo de configuração para ele. Segui a terminologia do Nuxt e criei um diretório de plugins no src . Dentro do diretório estão os axios.js index.js e axios.js .

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

Como mencionado acima, cada plug-in deve exportar uma função. Ao mesmo tempo, dentro da função, queremos ter acesso à loja e, posteriormente, a função 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    } 


Como você pode ver, o arquivo index.js também exporta a função. Isso é feito para poder passar o objeto do app lá. Agora vamos mudar main.js e chamar essa função.

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    


Resultado


Nesse estágio, conseguimos remover a configuração do plugin do main.js em um arquivo separado.

A propósito, o benefício de passar o objeto do app para todos os nossos plugins é que, dentro de cada plug-in, agora temos acesso à loja. Você pode usá-lo livremente chamando commit , dispatch , além de acessar store.state e store.getters .

Se você gosta do estilo ES6, pode até fazer o seguinte:

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

Segunda Etapa - Injeção de Dependência


Já criamos o primeiro plugin e agora nosso projeto se parece com o seguinte:

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

Como na maioria das bibliotecas onde é realmente necessário, a Injeção de Dependência já está implementada usando o Vue.use , criaremos nosso próprio plugin simples.

Por exemplo, tente repetir o que o vue-wait faz. Essa é uma biblioteca bastante pesada; portanto, se você quiser mostrar o carregador em um par de botões, é melhor abandoná-lo. No entanto, não pude resistir à sua conveniência e repeti em seu projeto sua funcionalidade básica, incluindo açúcar sintático na loja.

Aguarde plugin


Crie outro arquivo no diretório plugins - wait.js

Eu já tenho um módulo vuex, que também chamei de wait . Ele executa três etapas simples:

- start - define a propriedade state de um objeto chamado action como true
- end - remove do estado uma propriedade de um objeto chamado action
- is - obtém do estado uma propriedade de um objeto chamado action

Neste plugin, vamos usá-lo.

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); } 


E conecte nosso 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); } 


Injetar função


Agora implementamos a função 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]; } }); }); }; 


A magia do Vue.prototype


Agora sobre magia. A documentação do Vue diz que é suficiente escrever Vue.prototype.$appName = ' '; e $appName estarão disponíveis this .

No entanto, na realidade, descobriu-se que não é assim. Devido à pesquisa no Google, não houve resposta por que esse design não funcionou. Portanto, decidi entrar em contato com os autores do plugin que já implementaram isso.

Global mixin


Como no nosso exemplo, observei o código do plugin vue-wait . Eles oferecem essa implementação (o código-fonte é limpo para maior clareza):

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

Em vez de um protótipo, propõe-se o uso de uma mixina global. O efeito é basicamente o mesmo, talvez, com exceção de algumas nuances. Mas, como o injetor é feito aqui, não parece exatamente o caminho certo e não corresponde ao descrito na documentação.

Mas e se o protótipo?


A idéia por trás da solução de protótipo usada no inject função de inject foi emprestada do Nuxt. Parece muito mais correto do que o mixin global, então decidi.

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


Resultado


Após essas manipulações, temos a oportunidade de acessar this.$wait partir de qualquer componente, bem como de qualquer método na loja.

O que aconteceu


Estrutura do projeto:

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'); 

Conclusão


Como resultado das manipulações, recebemos uma importação e uma chamada de função no arquivo main.js E agora fica imediatamente claro onde procurar a configuração para cada plug-in e cada dependência global.

Ao adicionar um novo plugin, você só precisa criar um arquivo que exporte a função, importá-lo para index.js e chamar essa função.

Na minha prática, essa estrutura provou ser muito conveniente, além disso, é facilmente transferida de projeto para projeto. Agora não há problema se você precisar fazer a injeção de dependência ou configurar outro plug-in.

Compartilhe sua experiência com o gerenciamento de dependências nos comentários. Projetos de sucesso!

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


All Articles