Cómo organizar tus dependencias en una aplicación Vue

Todos los que están familiarizados con Vue saben que una aplicación Vue tiene un punto de entrada: el archivo main.js Allí, además de crear una instancia de Vue, hay una importación y una especie de inyección de dependencia de todas sus dependencias globales (directivas, componentes, complementos). Cuanto más grande es el proyecto, más dependencias se vuelven, lo que, además, cada uno tiene su propia configuración. Como resultado, obtenemos un archivo enorme con todas las configuraciones.
Este artículo discutirá cómo organizar dependencias globales para evitar esto.



¿Por qué escribirlo tú mismo?


Muchos pueden pensar: ¿por qué es esto necesario si hay, por ejemplo, Nuxt , que hará esto por usted? En mis proyectos, también lo usé, pero en proyectos simples esto puede ser redundante. Además, nadie ha cancelado proyectos con código heredado que caen sobre ti como la nieve en tu cabeza. Y conecte el marco allí, prácticamente hágalo desde cero.

Mente maestra


El organizador de tal organización fue Nuxt. Lo usé en un gran proyecto con Vue.
Nuxt tiene una gran característica: complementos. Cada complemento es un archivo que exporta una función. La configuración se pasa a la función, que también se pasará al constructor Vue al crear la instancia, así como a toda la tienda .

Además, una característica extremadamente útil, inject está disponible en cada complemento. Realiza una inyección de dependencia a la instancia raíz de Vue y al objeto de la store . Y esto significa que en cada componente, en cada función de almacenamiento, la dependencia especificada estará disponible a través de this .

¿Dónde puede ser útil esto?


Además del hecho de que main.js "pierde peso" significativamente, también tendrá la oportunidad de utilizar la dependencia en cualquier lugar de la aplicación sin importaciones innecesarias.

Un buen ejemplo de inyección de dependencias es vue-router . No se usa con mucha frecuencia: para obtener los parámetros de la ruta actual, para hacer una redirección, pero esta es una dependencia global. Si puede ser útil en cualquier componente, ¿por qué no hacerlo global? Además, gracias a esto, su estado también se almacenará globalmente y cambiará para toda la aplicación.

Otro ejemplo es vue-wait . Los desarrolladores de este complemento fueron más allá y agregaron la propiedad $wait no solo a la instancia de Vue, sino también a la tienda vuex. Dados los detalles del complemento, esto demuestra ser extremadamente útil. Por ejemplo, la tienda tiene una acción que se llama en varios componentes. Y en cada caso, debe mostrar el cargador en algún elemento. En lugar de llamar a $wait.start('action') y $wait.end('action') antes y después de cada llamada a la acción, simplemente puede llamar a estos métodos una vez en la acción misma. Y esto es mucho más legible y menos detallado que el dispatch('wait/start', 'action' {root: true}) . En el caso de la tienda, este es el azúcar sintáctico.

De las palabras al código


La estructura básica del proyecto.


Veamos cómo se ve el proyecto ahora:
src
- store
- App.vue
- main.js

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


Conectamos la primera dependencia


Ahora queremos conectar axios a nuestro proyecto y crear algún tipo de configuración para él. Seguí la terminología de Nuxt y creé un directorio de plugins en src . Dentro del directorio hay axios.js index.js y axios.js .

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

Como se mencionó anteriormente, cada complemento debe exportar una función. Al mismo tiempo, dentro de la función queremos tener acceso a la tienda y, posteriormente, a la función de 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 puede ver, el archivo index.js también exporta la función. Esto se hace para poder pasar el objeto de la app allí. Ahora main.js y llamemos a esta función.

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


En esta etapa, hemos logrado eliminar la configuración del complemento de main.js en un archivo separado.

Por cierto, el beneficio de pasar el objeto de la app a todos nuestros complementos es que dentro de cada complemento ahora tenemos acceso a la tienda. Puede usarlo libremente llamando a commit , dispatch y accediendo a store.state y store.getters .

Si te gusta el estilo ES6, incluso puedes hacer esto:

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

Segunda etapa: inyección de dependencia


Ya hemos creado el primer complemento y ahora nuestro proyecto se ve así:

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

Dado que en la mayoría de las bibliotecas donde es realmente necesario, la Inyección de dependencias ya está implementada usando Vue.use , crearemos nuestro propio complemento simple.

Por ejemplo, intente repetir lo que hace vue-wait . Esta es una biblioteca bastante pesada, por lo que si desea mostrar el cargador en un par de botones, es mejor abandonarlo. Sin embargo, no pude resistir su conveniencia y repetí en su proyecto su funcionalidad básica, incluido el azúcar sintáctico en la tienda.

Esperar complemento


Cree otro archivo en el directorio de plugins - wait.js

Ya tengo un módulo vuex, al que también llamé wait . Él hace tres pasos simples:

- start - establece la propiedad de estado de un objeto llamado action en true
- end - elimina del estado una propiedad de un objeto llamado action
- is - obtiene del estado una propiedad de un objeto llamado action

En este complemento lo usaremos.

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


Y conecta nuestro complemento:

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


Función de inyección


Ahora implementamos la función de 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 magia del prototipo Vue.


Ahora sobre magia. La documentación de Vue dice que es suficiente escribir Vue.prototype.$appName = ' '; y $appName estará disponible en this .

Sin embargo, en realidad resultó que esto no es así. Debido a googlear, no hubo respuesta por qué tal diseño no funcionó. Por lo tanto, decidí contactar a los autores del complemento que ya lo implementaron.

Mixin global


Como en nuestro ejemplo, miré el código del complemento vue-wait . Ofrecen dicha implementación (el código fuente se limpia para mayor claridad):

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

En lugar de un prototipo, se propone utilizar un mixin global. El efecto es básicamente el mismo, quizás, con la excepción de algunos matices. Pero dado que la inyección se realiza en la tienda aquí, no se ve exactamente de la manera correcta y no corresponde en absoluto a la documentación.

Pero, ¿y si el prototipo?


La idea detrás de la solución prototipo utilizada en el inject función de inject fue tomada de Nuxt. Parece mucho más correcto que el mixin global, así que me decidí por él.

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


Resultado


Después de estas manipulaciones, tenemos la oportunidad de acceder a this.$wait desde cualquier componente, así como desde cualquier método en la tienda.

Que paso


Estructura del proyecto:

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

Conclusión


Como resultado de las manipulaciones, recibimos una llamada de importación y una función en el archivo main.js Y ahora está claro de inmediato dónde buscar la configuración para cada complemento y cada dependencia global.

Al agregar un nuevo complemento, solo necesita crear un archivo que exporte la función, importarlo en index.js y llamar a esta función.

En mi práctica, dicha estructura ha demostrado ser muy conveniente, además, se transfiere fácilmente de un proyecto a otro. Ahora no hay dolor si necesita hacer una inyección de dependencia o configurar otro complemento.

Comparta su experiencia con la gestión de dependencias en los comentarios. Proyectos exitosos!

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


All Articles