Students Save 30%! Learn & create with unlimited courses & creative assets Students Save 30%! Save Now
Advertisement
  1. Code
  2. Vue
Code

Patrones de diseño para la comunicación entre componentes Vue.js

by
Difficulty:AdvancedLength:LongLanguages:

Spanish (Español) translation by Ana Paulina Figueroa Vazquez (you can also view the original English article)

Como desarrolladores, queremos generar un código gestionable y fácil de mantener, que también sea más sencillo de depurar y probar. Para hacer esto posible, adoptamos buenas prácticas conocidas como patrones. Los patrones son algoritmos y arquitecturas probadas, que nos ayudan a llevar a cabo tareas particulares de una manera eficiente y predecible.

En este tutorial veremos los patrones de comunicación más comunes para los componentes Vue.js, junto con algunos inconvenientes que debemos evitar. Todos sabemos que, en la vida real, no hay una solución única para todos los problemas. De la misma forma, en el desarrollo de aplicaciones con Vue.js, no hay un patrón universal para todos los escenarios de programación. Cada patrón tiene sus propias ventajas y desventajas, y cada uno es adecuado para casos de uso particulares. El aspecto esencial para los desarrolladores de Vue.js es conocer todos los patrones más comunes, para así poder elegir el adecuado para un proyecto determinado. Esto permitirá una comunicación entre componentes adecuada y eficiente.

¿Por qué es importante la comunicación adecuada entre componentes?

Cuando creamos una aplicación con un framework basado en componentes como Vue.js, nuestro objetivo es lograr que los componentes de nuestra aplicación estén tan aislados como sea posible. Esto los vuelve reutilizables, gestionables y comprobables. Para lograr que un componente sea reutilizable, necesitamos darle una forma más abstracta y desacoplada (o acoplada de manera flexible), y de esa manera podemos añadirlo a nuestra aplicación o quitarlo sin perjudicar la funcionalidad de esta última.

Sin embargo, no podemos lograr un aislamiento ni independencia totales en los componentes de nuestra aplicación. En algún momento necesitan comunicarse entre sí: para intercambiar datos, cambiar el estado de la aplicación, etcétera. Por lo tanto, es importante que aprendamos cómo lograr esta comunicación adecuadamente y al mismo tiempo mantener una aplicación funcional, flexible y escalable.

Resumen de la comunicación entre componentes de Vue.js

En Vue.js existen dos tipos principales de comunicación entre componentes:

  1. Comunicación directa padre-hijo, basada en las estrictas relaciones padre-a-hijo e hijo-a-padre
  2. Comunicación multicomponente, en la que un componente puede "hablar" con cualquier otro sin importar su relación.

En las siguientes secciones exploraremos ambos tipos junto con ejemplos apropiados.

Comunicación directa padre-hijo

El modelo estándar para la comunicación de componentes, con el que Vue.js es compatible de forma inmediata, es el modelo padre-hijo que se lleva a cabo a través de props y eventos personalizados. En el siguiente diagrama puedes ver una representación visual de la manera en la que se ve este modelo en acción.

Como puedes observar, un padre puede comunicarse solamente con sus hijos directos, y los hijos pueden comunicarse directamente solo con sus padres. En este modelo no es posible la comunicación entre hermanos ni la multicomponente.

En las siguientes secciones tomaremos los componentes del diagrama anterior y los implementaremos en una serie de ejemplos prácticos.

Comunicación padre-a-hijo

Supongamos que los componentes que tenemos son parte de un juego. La mayoría de los juegos muestran la puntuación en algún lugar de sus interfaces. Imagina que tenemos una variable score declarada en el componente Parent A, y queremos mostrarla en el componente Child A. ¿Cómo podemos hacer eso?.

Para enviar datos de un padre a sus hijos Vue.js usa props. Se requieren tres pasos para enviar una propiedad:

  1. Registrar la propiedad en el hijo, de esta manera props: ["score"]
  2. Usar la propiedad registrada en la plantilla del hijo, de esta manera <span>Score: {{ score }}</span>
  3. Vincular la propiedad a la variable score (en la plantilla del padre), de esta forma <child-a :score="score"/>

Exploremos el ejemplo completo para comprender mejor qué es lo que verdaderamente ocurre:

Ejemplo en CodePen

Validando Props

Para mayor brevedad y claridad, he registrado los props usando sus variantes abreviadas. Pero en el desarrollo real se recomienda validar los props. Esto garantizará que los props reciban el tipo de valor correcto. Por ejemplo, nuestra propiedad score puede ser validada así:

Al usar props, por favor asegúrate de comprender la diferencia entre sus variantes literales y dinámicas. Un prop es dinámico cuando lo vinculamos a alguna variable (por ejemplo, v-bind:score="score" o su abreviatura :score="score"), y por lo tanto el valor del prop cambiará dependiendo del valor de la variable. Si solamente colocamos un valor sin vincular, entonces ese valor será interpretado literalmente y el resultado será estático. En nuestro caso, si lo escribimos como score="score", este mostrará score en vez de 100. Este es un prop literal. Debes tener cuidado con esta sutil diferencia.

Actualizando un prop hijo

Hasta ahora hemos mostrado con éxito la puntuación del juego, pero en algún momento necesitaremos actualizarlo. Vamos a intentarlo.

Creamos un método changeScore(), que debe actualizar la puntuación después de que presionemos el botón Change Score (Cambiar puntuación). Cuando hacemos esto, parece como si la puntuación se actualizara correctamente, pero recibimos la siguiente advertencia de Vue en la consola:

[advertencia de Vue]: Evita mutar un prop directamente ya que el valor se sobrescribirá cada vez que el componente padre se vuelva a mostrar. En vez de eso, usa una propiedad de datos o una calculada en base al valor del prop. Prop siendo mutado: "score"

Como puedes ver, Vue nos indica que el prop será sobrescrito si el padre vuelve a mostrarse. Hagamos una prueba de esto simulando ese comportamiento con el método integrado $forceUpdate():

Ejemplo en CodePen

Ahora bien, cuando cambiamos la puntuación y luego presionamos el botón Rerender Parent (Volver a mostrar al padre), podemos ver que la puntuación regresa a su valor inicial del padre. ¡Vue está diciendo la verdad!.

Sin embargo, ten en mente que los arreglos y los objetos afectarán a sus padres, debido a que no son copiados sino enviamos por referencia.

Por lo tanto, cuando necesitamos mutar un prop en el hijo existen dos maneras de solucionar este efecto secundario al volver a mostrar el componente.

Cómo mutar un prop con una propiedad de datos local

El primer método es convertir el prop score a una propiedad de datos local (localScore), para usarla en el método changeScore() y en la plantilla:

Ejemplo de CodePen

Ahora, si presionamos el botón Rerender Parent (Volver a mostrar al padre) de nuevo, después de haber cambiado la puntuación, veremos que esta vez su valor permanece igual.

Cómo mutar un prop con una propiedad calculada

El segundo método es usar el prop score en una propiedad calculada, en donde será transformado a un nuevo valor:

Ejemplo de CodePen

Aquí hemos creado doubleScore() calculado, que multiplica el score del padre por dos y luego el resultado se muestra en la plantilla. Obviamente, presionar el botón Rerender Parent (Volver a mostrar al padre) no causará ningún efecto secundario.

Comunicación hijo-a-padre

Ahora veamos cómo es que los componentes pueden comunicarse en la dirección opuesta.

Acabamos de ver cómo mutar un prop en el hijo, pero ¿qué ocurre si necesitamos usar ese prop en más de un componente hijo?. En ese caso necesitaremos mutar el prop desde su fuente en el padre, para que todos los componentes que usen el prop sean actualizados correctamente. Para satisfacer este requisito, Vue presenta los custom events.

El principio aquí es que debemos notificar al padre con el cambio que queramos hacer, el padre hace el cambio, y ese cambio es reflejado a través de prop enviado. Estos son los pasos necesarios para esta operación:

  1. En el hijo, vamos a emitir un evento describiendo el cambio que queramos hacer, de esta forma this.$emit('updatingScore', 200)
  2. En el padre registramos un detector de eventos para el evento emitido, de esta manera @updatingScore="updateScore"
  3. Cuando el evento sea emitido, el método asignado actualizará el prop de esta forma this.score = newValue

Exploremos el ejemplo completo para comprender mejor cómo es que esto ocurre:

Ejemplo de CodePen

Usamos el método integrado $emit() para emitir un evento. El método recibe dos argumentos. El primer es el evento que queremos emitir, y el segundo es el valor nuevo.

El modifier .sync

Vue ofrece un modifier .sync que funciona de manera similar y quizá queramos usarlo como atajo rápido en algunos casos. En una situación así, usamos el método $emit() de una manera ligeramente diferente. Como argumento del evento colocamos update:score de esta forma this.$emit('update:score', 200), y luego, cuando vinculemos el prop score, vamos a añadir el modifier .sync de esta forma <child-a:score.sync="score"/> En el componente Parent A, eliminamos el método updateScore() y el registro del evento (@updatingScore="updateScore"), ya que ya no serán necesarios.

Ejemplo de CodePen

Por qué no usar this.$parent y this.$children para la comunicación directa padre-hijo?

Vue ofrece dos métodos API que nos proporcionan acceso directo a los componentes padre e hijo: this.$parent y this.$children. Al principio puede ser tentador usarlos como una alternativa rápida y más sencilla a los props y eventos, pero no debemos hacerlo. Esta se considera una mala práctica, o algo que va en contra de los patrones, debido a que forma una unión estrecha entre los componentes padre e hijo. Esto último da como resultado componentes inflexibles y fáciles de arruinar, que son difíciles de depurar y entender. Estos métodos API se usan en raras ocasiones, y como regla general debemos evitarlos o usarlos con precaución.

Comunicación entre componentes de dos vías

Los props y los eventos son unidireccionales. Los props van hacia abajo y los eventos hacia arriba. Pero mediante el uso de props y eventos en conjunto, podemos comunicarnos en ambas direcciones del árbol de componentes efectivamente, lo que da como resultado una vinculación de datos en dos vías. Esto es lo que la directiva v-model hace internamente.

Comunicación multicomponente

El patrón de comunicación padre-hijo rápidamente se vuelve inconveniente y poco práctico a medida que la complejidad de nuestra aplicación aumenta. El problema con el sistema de props-eventos es que funciona directamente y está vinculado estrechamente al árbol de componentes. Los eventos Vue no emergen, a diferencia de los nativos, y es por eso que necesitamos emitirlos repetidamente hasta llegar al objetivo. Como resultado nuestro código se satura con demasiados detectores y emisores de eventos. Por esta razón, en aplicaciones más complejas debemos considerar el uso de un patrón de comunicación multicomponente.

Echemos un vistazo al siguiente diagrama:

Como podemos ver, en este tipo de comunicación de cualquiera-a-cualquiera, cada componente puede enviar y/o recibir datos de cualquier otro componente sin la necesidad de pasos intermedios ni componentes intermediarios.

En las siguientes secciones exploraremos las implementaciones más comunes para la comunicación multicomponente

Bus de evento global

Un bus de evento global es una instancia de Vue que usamos para emitir y detectar eventos. Vamos a verlo en práctica.

Ejemplo de CodePen

Estos son los pasos para crear y usar un bus de evento:

  1. Declarar nuestro bus de evento como una nueva instancia Vue, de esta forma const eventBus = new Vue ()
  2. Emitir un evento desde el componente fuente, de esta manera eventBus.$emit('updatingScore', 200)
  3. Detectar el evento emitido en el componente destino, de esta forma eventBus.$on('updatingScore', this.updateScore)

En el código de ejemplo anterior, eliminamos @updatingScore="updateScore" del hijo y en vez de eso usamos el ciclo de vida created() para detectar el evento updatingScore. Cuando se emita el evento, el método updateScore() será ejecutado. También podemos enviar el método para la actualización como una función anónima:

El patrón de bus de evento global puede resolver el problema de la saturación de eventos hasta cierto punto, pero introduce otras dificultades. Los datos de la aplicación pueden ser modificados desde cualquier parte de la aplicación sin dejar rastro. Esto complica la depuración y las pruebas. Para las aplicaciones más complejas, en las que todo puede salirse de control rápidamente, debemos considerar un patrón de gestión de estados dedicado, tal como Vuex, que nos dará un control más granular, una mejor estructura y organización del código, así como un útil seguimiento de los cambios y herramientas para la depuración.

Vuex

Vuex es una biblioteca de gestión de estados diseñada para crear aplicaciones de Vue.js complejas y escalables. El código escrito con Vuex es más detallado, pero puede dar mejores resultados a largo plazo. Usa un almacenamiento centralizado para todos los componentes de una aplicación, mejorando la organización y transparencia de nuestras aplicaciones y haciéndolas más fáciles de depurar y seguir. El almacenamiento es totalmente reactivo, así que los cambios que hagamos se verán reflejados instantáneamente.

Aquí te proporciono una breve explicación de lo que es Vuex, así como un ejemplo contextual. Si quieres adentrarte más en Vuex te sugiero que consultes mi tutorial sobre la construcción de aplicaciones complejas con Vuex.

Ahora exploremos el siguiente diagrama:

Como puedes ver, una aplicación Vuex está compuesta por cuatro partes distintivas:

  • State es en donde tenemos los datos de nuestra aplicación.
  • Getters son métodos para acceder el estado de almacenamiento y enviarlo a los componentes.
  • Mutations son los únicos métodos reales que tienen permiso para mutar el estado.
  • Actions son los métodos para ejecutar código asíncrono y desencadenar mutaciones.

Vamos a crear un simple almacenamiento y veamos cómo funciona todo esto en acción.

Ejemplo de CodePen

En el almacenamiento tenemos lo siguiente:

  • Una variable score establecida en el objeto state.
  • Una mutación incrementScore(), que incrementará la puntuación con un valor determinado.
  • Un getter score(), que accederá a la variable score desde el estado y la enviará a los componentes.
  • Una acción incrementScoreAsync(), que usará la mutación incrementScore() para incrementar la puntuación después de un período determinado de tiempo.

En la instancia Vue, en vez de props usamos propiedades calculadas para obtener el valor de la puntuación a través de getters. Después, para cambiar la puntuación, en el componente Child A usamos la mutación store.commit('incrementScore', 100). En el componente Parent B usamos la acción store.dispatch('incrementScoreAsync', 3000).

Inyección de dependencias

Antes de terminar vamos a explorar un patrón más. Sus casos de uso se relacionan principalmente con bibliotecas de componentes compartidas y complementos, pero vale la pena mencionarlo para complementar.

La inyección de dependencias nos permite definir un servicio a través de la propiedad provide, que debe ser un objeto o una función que devuelva un objeto, y lo vuelve disponible para todos los descendientes del componente, no solamente para sus hijos directos. Luego podemos consumir ese servicio a través de la propiedad inject.

Veamos esto en acción:

Ejemplo de CodePen

Mediante el uso de la opción provide en el componente Grand Parent, permitimos que la variable score esté disponible para todos sus descendientes. Cada uno puede obtener acceso declarando la propiedad inject: ['score']. Y, como puedes ver, la puntuación es mostrada en todos los componentes.

Nota: Los vínculos creados por la inyección de dependencias no son reactivos. Por lo tanto, si queremos que los cambios hechos en el componente proveedor se vean reflejados en sus descendientes, tenemos que asignar un objeto a una propiedad de datos y usarlo en el servicio proporcionado.

¿Por qué no usar this.$root para la comunicación multicomponente?

Las razones por las que no debemos usar this.$root son similares a las explicadas anteriormente para this.$parent y this.$children — esto crea demasiadas dependencias entre ellos. Debe evitarse confiar en cualquiera de estos métodos para la comunicación entre componentes.

¿Cómo elegir el patrón adecuado?

Así que, ya conoces todas las maneras comunes para crear una comunicación entre componentes. Pero ¿cómo decidir cuál se ajusta mejor a tu escenario?

La elección del patrón adecuado depende del proyecto en el que estés involucrado o de la aplicación que quieras crear. Depende de la complejidad y el tipo de tu aplicación. Vamos a explorar los escenarios más comunes:

  • En aplicaciones simples los props y eventos serán todo lo que necesitarás.
  • En aplicaciones de alcance medio se requerirán maneras más flexibles de comunicación, por ejemplo bus de eventos e inyección de dependencias.
  • Para aplicaciones complejas a gran escala definitivamente necesitarás el poder de Vuex como un sistema de gestión de estado que cuenta con todas las funciones.

Y por último, no es necesario que uses ninguno de los patrones que hemos explorado solamente porque alguien te dijo que lo hicieras. Eres libre de elegir y usar cualquier patrón que quieras, siempre y cuando logres que tu aplicación siga funcionando y que siga siendo fácil de mantener y escalar.

Conclusión

En este tutorial aprendimos sobre los patrones de comunicación entre componentes más comunes de Vue.js. Vimos cómo implementarlos en la práctica y cómo elegir el adecuado, aquel que se ajuste mejor a nuestro proyecto. Esto garantizará que la aplicación que hayamos construido use el tipo adecuado de comunicación entre componentes, lo que la vuelve completamente funcional, gestionable, comprobable y escalable.

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.