Cómo construir aplicaciones Vue.js complejas a gran escala con Vuex

Es tan fácil de aprender y usar Vue.js que cualquiera puede crear una aplicación simple con ese marco. Incluso los principiantes, con la ayuda de la documentación de Vue, pueden hacer el trabajo. Sin embargo, cuando la complejidad entra en juego, las cosas se ponen un poco más serias. La verdad es que los componentes múltiples y profundamente anidados con el estado compartido pueden convertir rápidamente su aplicación en un desorden inigualable..

El problema principal en una aplicación compleja es cómo administrar el estado entre componentes sin escribir código spaghetti o producir efectos secundarios. En este tutorial, aprenderá cómo resolver ese problema utilizando Vuex: una biblioteca de administración de estado para crear aplicaciones complejas de Vue.js.

Que es vuex?

Vuex es una biblioteca de administración de estado específicamente optimizada para crear aplicaciones Vue.js complejas a gran escala. Utiliza un almacén global y centralizado para todos los componentes de una aplicación, aprovechando su sistema de reactividad para actualizaciones instantáneas..

La tienda Vuex está diseñada de tal manera que no es posible cambiar su estado desde ningún componente. Esto asegura que el estado solo puede ser mutado de una manera predecible. Por lo tanto, su tienda se convierte en una fuente única de verdad: cada elemento de datos solo se almacena una vez y es de solo lectura para evitar que los componentes de la aplicación dañen el estado al que acceden otros componentes..

¿Por qué necesitas Vuex??

Puede preguntar: ¿Por qué necesito Vuex en primer lugar? ¿No puedo poner el estado compartido en un archivo JavaScript normal e importarlo a mi aplicación Vue.js??

Puede, por supuesto, pero en comparación con un objeto global simple, la tienda Vuex tiene algunas ventajas y beneficios significativos:

  • La tienda Vuex es reactiva. Una vez que los componentes recuperan un estado de ellos, actualizarán sus vistas de manera reactiva cada vez que el estado cambie..
  • Los componentes no pueden mutar directamente el estado de la tienda. La única forma de cambiar el estado de la tienda es mediante el envío explícito de mutaciones. Esto asegura que cada cambio de estado deje un registro rastreable, lo que hace que la aplicación sea más fácil de depurar y probar.
  • Puede depurar fácilmente su aplicación gracias a la integración de Vuex con la extensión DevTools de Vue.
  • La tienda Vuex le ofrece una vista panorámica de cómo todo está conectado y afectado en su aplicación..
  • Es más fácil mantener y sincronizar el estado entre varios componentes, incluso si la jerarquía de componentes cambia.
  • Vuex hace posible la comunicación directa de componentes cruzados.
  • Si se destruye un componente, el estado en la tienda Vuex permanecerá intacto.

Empezando con Vuex

Antes de empezar, quiero aclarar varias cosas.. 

Primero, para seguir este tutorial, debe tener una buena comprensión de Vue.js y su sistema de componentes, o al menos una experiencia mínima con el marco. 

Además, el objetivo de este tutorial no es mostrarle cómo crear una aplicación compleja real; el objetivo es centrar su atención más en los conceptos de Vuex y en cómo puede usarlos para crear aplicaciones complejas. Por esa razón, voy a usar ejemplos muy simples y sin ningún código redundante. Una vez que haya comprendido completamente los conceptos de Vuex, podrá aplicarlos en cualquier nivel de complejidad..

Finalmente, estaré usando la sintaxis ES2015. Si no estás familiarizado con él, puedes aprenderlo aquí..

Y ahora, comencemos!

Configuración de un proyecto Vuex

El primer paso para comenzar con Vuex es tener Vue.js y Vuex instalados en su máquina. Hay varias formas de hacerlo, pero usaremos la más fácil. Simplemente cree un archivo HTML y agregue los enlaces CDN necesarios:

            

Usé algo de CSS para hacer que los componentes se vieran mejor, pero no tienes que preocuparte por ese código CSS. Solo le ayuda a obtener una idea visual de lo que está sucediendo. Solo copia y pega lo siguiente dentro de la  etiqueta:

Ahora, vamos a crear algunos componentes para trabajar. Dentro de > etiqueta, justo encima del cierre  etiqueta, ponga el siguiente código de Vue:

Vue.component ('ChildB', template: ' 

Puntuación:

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

Puntuación:

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

Puntuación:

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

Aquí tenemos una instancia de Vue, un componente principal y dos componentes secundarios. Cada componente tiene un encabezado "Puntuación:"donde emitiremos el estado de la aplicación.

Lo último que debes hacer es poner un envoltorio.

 con id = "aplicación" justo después de la apertura , y luego coloque el componente padre dentro de:

El trabajo de preparación ya está hecho y estamos listos para seguir adelante..

Explorando Vuex

Administración del Estado

En la vida real, enfrentamos la complejidad mediante el uso de estrategias para organizar y estructurar el contenido que queremos usar. Agrupamos cosas relacionadas en diferentes secciones, categorías, etc. Es como una biblioteca de libros, en la que los libros se categorizan y se colocan en diferentes secciones para que podamos encontrar fácilmente lo que estamos buscando. Vuex organiza los datos de aplicación y la lógica relacionada con el estado en cuatro grupos o categorías: estado, captadores, mutaciones y acciones.

El estado y las mutaciones son la base de cualquier tienda Vuex:

  • estado Es un objeto que contiene el estado de los datos de la aplicación..
  • mutaciones También es un objeto que contiene métodos que afectan el estado..

Los captadores y las acciones son como proyecciones lógicas de estado y mutaciones:

  • captadores contiene métodos utilizados para abstraer el acceso al estado y para realizar algunos trabajos de preprocesamiento, si es necesario (cálculo de datos, filtrado, etc.).
  • comportamiento Son métodos utilizados para desencadenar mutaciones y ejecutar código asíncrono..

Exploremos el siguiente diagrama para aclarar un poco las cosas:

En el lado izquierdo, tenemos un ejemplo de una tienda Vuex, que crearemos más adelante en este tutorial. En el lado derecho, tenemos un diagrama de flujo de trabajo de Vuex, que muestra cómo los diferentes elementos de Vuex trabajan juntos y se comunican entre sí..

Para cambiar el estado, un componente particular de Vue debe cometer mutaciones (por ejemplo,. este. $ store.commit ('incremento', 3)), y luego, esas mutaciones cambian el estado (Puntuación se convierte en 3). Después de eso, los captadores se actualizan automáticamente gracias al sistema reactivo de Vue, y representan las actualizaciones en la vista del componente (con este. $ store.getters.score). 

Las mutaciones no pueden ejecutar código asíncrono, ya que esto haría imposible registrar y rastrear los cambios en herramientas de depuración como Vue DevTools. Para usar la lógica asíncrona, es necesario ponerlo en acciones. En este caso, un componente primero enviará acciones (este. $ store.dispatch ('incrementScore', 3000)) donde se ejecuta el código asíncrono, y luego esas acciones cometen mutaciones, lo que mutará el estado. 

Crear un esqueleto de la tienda Vuex

Ahora que hemos explorado cómo funciona Vuex, creemos el esqueleto de nuestra tienda Vuex. Coloque el siguiente código sobre el ChildB registro de componentes:

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

Para proporcionar acceso global a la tienda Vuex desde cada componente, debemos agregar el almacenar Propiedad en la instancia de Vue:

nuevo Vue (el: '#app', tienda // registrar la tienda Vuex globalmente)

Ahora, podemos acceder a la tienda desde cada componente con el esto. $ tienda variable.

Hasta ahora, si abre el proyecto con CodePen en el navegador, debería ver el siguiente resultado.

Propiedades del estado

El objeto de estado contiene todos los datos compartidos en su aplicación. Por supuesto, si es necesario, cada componente también puede tener su propio estado privado.

Imagina que quieres crear una aplicación de juego y necesitas una variable para almacenar la puntuación del juego. Así que lo pones en el objeto estatal:

estado: puntuación: 0

Ahora, puedes acceder directamente al puntaje del estado. Volvamos a los componentes y reutilicemos los datos de la tienda. Para poder reutilizar los datos reactivos del estado de la tienda, debe usar las propiedades computadas. Así que vamos a crear una Puntuación() Propiedad calculada en el componente padre:

computed: score () return this. $ store.state.score

En la plantilla del componente padre, ponga la Puntuación expresión:

Puntuación: puntuación

Y ahora, haz lo mismo con los dos componentes hijos..

Vuex es tan inteligente que hará todo el trabajo para actualizar de forma reactiva el Puntuación Propiedad siempre que el estado cambie. Intente cambiar el valor de la puntuación y vea cómo se actualiza el resultado en los tres componentes.

Creando Getters

Es bueno, por supuesto, que puedas reutilizar el este. $ store.state palabra clave dentro de los componentes, como se vio arriba. Pero imagina los siguientes escenarios:

  1. En una aplicación a gran escala, donde múltiples componentes acceden al estado de la tienda utilizando este. $ store.state.score, usted decide cambiar el nombre de Puntuación. Esto significa que tiene que cambiar el nombre de la variable dentro de cada componente que lo usa! 
  2. Desea utilizar un valor computado del estado. Por ejemplo, supongamos que desea otorgar a los jugadores una bonificación de 10 puntos cuando el puntaje llegue a 100 puntos. Entonces, cuando la puntuación llega a 100 puntos, se agregan 10 puntos de bonificación. Esto significa que cada componente debe contener una función que reutiliza la puntuación y la incrementa en 10. Tendrás un código repetido en cada componente, que no es bueno en absoluto!

Afortunadamente, Vuex ofrece una solución de trabajo para manejar tales situaciones. Imagine el captador centralizado que accede al estado de la tienda y proporciona una función de captador a cada uno de los elementos del estado. Si es necesario, este captador puede aplicar algunos cálculos al elemento del estado. Y si necesita cambiar los nombres de algunas de las propiedades del estado, solo puede cambiarlos en un lugar, en este buscador. 

Vamos a crear un Puntuación() adquiridor:

getters: score (state) return state.score

Un captador recibe el estado como su primer argumento, y luego lo usa para acceder a las propiedades del estado.

Nota: Los recolectores también reciben captadores como el segundo argumento. Puedes usarlo para acceder a los otros captadores en la tienda..

En todos los componentes, modifique el Puntuación() propiedad calculada para utilizar el Puntuación() getter en lugar de la puntuación del estado directamente.

computed: score () return this. $ store.getters.score

Ahora, si decides cambiar el Puntuación a resultado, necesita actualizarlo solo en un lugar: en el Puntuación() adquiridor. Pruébalo en este CodePen!

Creando mutaciones

Las mutaciones son la única forma permisible de cambiar el estado. Desencadenar cambios simplemente significa cometer mutaciones en métodos de componentes.

Una mutación es prácticamente una función de controlador de eventos que se define por su nombre. Las funciones del controlador de mutación reciben un estado como primer argumento También puede pasar un segundo argumento adicional, que se llama carga útil por la mutación. 

Vamos a crear un incremento() mutación:

mutaciones: incremento (estado, paso) estado.scora + = paso

¡Las mutaciones no pueden ser llamadas directamente! Para realizar una mutación, debes llamar al cometer() Método con el nombre de la mutación correspondiente y posibles parámetros adicionales. Podría ser solo uno, como el paso en nuestro caso, o puede haber varios envueltos en un objeto.

Vamos a usar el incremento() mutación en los dos componentes secundarios mediante la creación de un método denominado changeScore ():

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

Estamos cometiendo una mutación en lugar de cambiar. este. $ store.state.score directamente, porque queremos rastrear explícitamente el cambio realizado por la mutación. De esta manera, hacemos que la lógica de nuestra aplicación sea más transparente, rastreable y fácil de razonar. Además, hace posible implementar herramientas, como Vue DevTools o Vuetron, que pueden registrar todas las mutaciones, tomar instantáneas de estado y realizar depuración de viajes en el tiempo.

Ahora, vamos a poner el changeScore () Método en uso. En cada plantilla de los dos componentes secundarios, cree un botón y agregue un detector de eventos de clic:

Al hacer clic en el botón, el estado se incrementará en 3, y este cambio se reflejará en todos los componentes. Ahora hemos logrado efectivamente la comunicación directa de componentes cruzados, lo que no es posible con el mecanismo de "props down, events up" integrado de Vue.js. Compruébalo en nuestro ejemplo CodePen.

Creando Acciones

Una acción es solo una función que comete una mutación. Cambia el estado indirectamente, lo que permite la ejecución de operaciones asíncronas.. 

Vamos a crear un incrementScore () acción:

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

Las acciones obtienen el contexto como primer parámetro, que contiene todos los métodos y propiedades de la tienda. Por lo general, solo extraemos las partes que necesitamos mediante la desestructuración de argumentos de ES2015. los cometer El método es uno que necesitamos muy a menudo. Las acciones también obtienen un segundo argumento de carga útil, al igual que las mutaciones.

En el ChildB componente, modificar el changeScore () método:

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

Para llamar a una acción, utilizamos el envío() Método con el nombre de la acción correspondiente y parámetros adicionales, al igual que con las mutaciones..

Ahora el Cambiar puntaje botón de la NiñoA componente incrementará la puntuación en 3. El botón idéntico del ChildB El componente hará lo mismo, pero después de un retraso de 3 segundos. En el primer caso, ejecutamos código síncrono y usamos una mutación, pero en el segundo caso ejecutamos código asíncrono y necesitamos una acción en su lugar. Vea cómo funciona todo en nuestro ejemplo de CodePen.

Vuex Mapping Helpers

Vuex ofrece algunos ayudantes útiles que pueden simplificar el proceso de creación de estados, captadores, mutaciones y acciones. En lugar de escribir esas funciones manualmente, podemos decirle a Vuex que las cree por nosotros. Vamos a ver cómo funciona.

En lugar de escribir el Puntuación() Propiedad calculada como esta:

computed: score () return this. $ store.state.score

Solo usamos el mapState () ayudante como este:

calculado: … Vuex.mapState (['score'])

Y el Puntuación() La propiedad se crea automáticamente para nosotros..

Lo mismo es cierto para los captadores, mutaciones y acciones.. 

Para crear el Puntuación() getter, usamos el mapGetters () ayudante:

calculado: … Vuex.mapGetters (['score'])

Para crear el changeScore () método, usamos el mapMutaciones () ayudante como este:

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

Cuando se usa para mutaciones y acciones con el argumento de carga útil, debemos pasar ese argumento en la plantilla donde definimos el controlador de eventos:

Si queremos changeScore () Para usar una acción en lugar de una mutación, usamos mapActions () Me gusta esto:

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

De nuevo, debemos definir el retraso en el controlador de eventos:

Nota: Todos los ayudantes de mapeo devuelven un objeto. Por lo tanto, si queremos usarlos en combinación con otras propiedades o métodos locales calculados, necesitamos fusionarlos en un objeto. Afortunadamente, con el operador de propagación de objetos (... ), podemos hacerlo sin utilizar ninguna utilidad.. 

En nuestro CodePen, puede ver un ejemplo de cómo se utilizan todos los ayudantes de mapeo en la práctica.

Haciendo la tienda más modular

Parece que el problema con la complejidad constantemente obstruye nuestro camino. Lo resolvimos antes mediante la creación de la tienda Vuex, donde facilitamos la administración del estado y la comunicación de los componentes. En esa tienda, tenemos todo en un solo lugar, fácil de manipular y fácil de razonar acerca de. 

Sin embargo, a medida que nuestra aplicación crece, este archivo de tienda fácil de administrar se hace cada vez más grande y, como resultado, más difícil de mantener. Nuevamente, necesitamos algunas estrategias y técnicas para mejorar la estructura de la aplicación devolviéndola a su forma fácil de mantener. En esta sección, exploraremos varias técnicas que pueden ayudarnos en esta tarea..

Usando los módulos Vuex

Vuex nos permite dividir el objeto de la tienda en módulos separados. Cada módulo puede contener su propio estado, mutaciones, acciones, captadores y otros módulos anidados. Después de haber creado los módulos necesarios, los registramos en la tienda..

Vamos a verlo en acción:

const childB = state: result: 3, getters: result (state) return state.result, mutaciones: Increase (state, step) state.result + = step, acciones: IncreaseResult : (commit, delay) => setTimeout (() => commit ('Increase', 6), delay) const childA = state: score: 0, getters: score ( state) return state.score, mutaciones: incremento (state, step) state.score + = step, acciones: incrementScore: (commit, delay) => setTimeout (() => commit ('increment', 3), delay) const store = new Vuex.Store (modules: scoreBoard: childA, resultBoard: childB)

En el ejemplo anterior, creamos dos módulos, uno para cada componente secundario. Los módulos son objetos simples, que registramos como marcador y resultadoBoard en el módulos Objeto dentro de la tienda. El código para niñoA es el mismo que en la tienda de los ejemplos anteriores. En el código de niñoB, Añadimos algunos cambios en los valores y nombres..

Ahora vamos a ajustar la ChildB componente para reflejar los cambios en el resultadoBoard módulo. 

Vue.component ('ChildB', template: ' 

Resultado: resultado

', computed: result () devolver esto. $ store.getters.result, métodos: changeResult () this. $ store.dispatch (' IncreaseResult ', 3000); )

En el NiñoA componente, lo único que necesitamos modificar es el changeScore () método:

Vue.component ('ChildA', template: ' 

Puntuación: puntuación

', computed: score () return this. $ store.getters.score, métodos: changeScore () this. $ store.dispatch (' incrementScore ', 3000); )

Como puede ver, dividir la tienda en módulos lo hace mucho más liviano y fácil de mantener, al mismo tiempo que mantiene su gran funcionalidad. Echa un vistazo al CodePen actualizado para verlo en acción..

Módulos de espacio de nombre

Si desea o necesita usar el mismo nombre para una propiedad o método en particular en sus módulos, entonces debe considerar el espacio entre ellos. De lo contrario, puede observar algunos efectos secundarios extraños, como ejecutar todas las acciones con los mismos nombres o obtener los valores del estado incorrecto.. 

Para nombrar un módulo Vuex, solo tiene que configurar espacio de nombre propiedad a cierto.

const childB = namespaced: true, state: score: 3, getters: score (state) return state.score, mutaciones: increment (state, step) state.score + = step, acciones: incrementScore: (commit, delay) => setTimeout (() => commit ('increment', 6), delay) const childA = namespaced: true, state: score: 0, getters: score (state) return state.score, mutations: increment (state, step) state.score + = step, acciones: incrementScore: (commit, delay) = > setTimeout (() => commit ('increment', 3), delay)

En el ejemplo anterior, hicimos los nombres de propiedades y métodos iguales para los dos módulos. Y ahora podemos usar una propiedad o método prefijado con el nombre del módulo. Por ejemplo, si queremos usar el Puntuación() getter de la resultadoBoard módulo, lo escribimos así: resultadoBoard / puntuación. Si queremos el Puntuación() getter de la marcador módulo, entonces lo escribimos así: scoreBoard / score

Ahora modifiquemos nuestros componentes para reflejar los cambios que hicimos.. 

Vue.component ('ChildB', template: ' 

Resultado: resultado

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

Puntuación: puntuación

', computed: score () return this. $ store.getters [' scoreBoard / score '], métodos: changeScore () this. $ store.dispatch (' scoreBoard / incrementScore ', 3000); )

Como puede ver en nuestro ejemplo de CodePen, ahora podemos usar el método o la propiedad que queremos y obtener el resultado que esperamos..

Dividiendo la tienda Vuex en archivos separados

En la sección anterior, mejoramos la estructura de la aplicación hasta cierto punto al separar la tienda en módulos. Hicimos que la tienda fuera más limpia y organizada, pero aún así, todo el código de la tienda y sus módulos se encuentran en el mismo archivo grande. 

Entonces, el siguiente paso lógico es dividir el almacén de Vuex en archivos separados. La idea es tener un archivo individual para la tienda en sí y uno para cada uno de sus objetos, incluidos los módulos. Esto significa tener archivos separados para el estado, captadores, mutaciones, acciones y para cada módulo individual (store.jsestado.js, getters.js, etc.) Puede ver un ejemplo de esta estructura al final de la siguiente sección..

Usando los componentes de Vue Single File

Hemos hecho que la tienda Vuex sea lo más modular posible. Lo siguiente que podemos hacer es aplicar la misma estrategia a los componentes Vue.js también. Podemos poner cada componente en un único archivo autocontenido con un .vue extensión. Para saber cómo funciona esto, puede visitar la página de documentación de Vue Single File Components. 

Entonces, en nuestro caso, tendremos tres archivos: Parent.vueChildA.vue, y ChildB.vue

Finalmente, si combinamos las tres técnicas, terminaremos con la siguiente estructura o similar:

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

En nuestro tutorial repositorio de GitHub, puede ver el proyecto completado con la estructura anterior.

Resumen

Vamos a resumir algunos puntos principales que debe recordar acerca de Vuex:

Vuex es una biblioteca de administración estatal que nos ayuda a crear aplicaciones complejas a gran escala. Utiliza una tienda centralizada global para todos los componentes de una aplicación. Para abstraer el estado, utilizamos captadores. Los captadores son muy parecidos a las propiedades computadas y son una solución ideal cuando necesitamos filtrar o calcular algo en tiempo de ejecución.

La tienda Vuex es reactiva y los componentes no pueden mutar directamente el estado de la tienda. La única forma de mutar el estado es mediante la confirmación de mutaciones, que son transacciones sincrónicas. Cada mutación debe realizar una sola acción, debe ser lo más simple posible y solo es responsable de actualizar una parte del estado..

La lógica asíncrona debe encapsularse en acciones. Cada acción puede cometer una o más mutaciones, y una mutación puede ser cometida por más de una acción. Las acciones pueden ser complejas, pero nunca cambian el estado directamente.

Finalmente, la modularidad es la clave para la mantenibilidad. Para hacer frente a la complejidad y hacer que nuestro código sea modular, utilizamos el principio de "divide y vencerás" y la técnica de división de código..

Conclusión

¡Eso es! Ya conoce los conceptos principales detrás de Vuex y está listo para comenzar a aplicarlos en la práctica..  

Por razones de brevedad y simplicidad, omití intencionalmente algunos detalles y características de Vuex, por lo que deberá leer la documentación completa de Vuex para aprender todo sobre Vuex y su conjunto de características..