Uno de los desafíos de empezar a usar Vue cuando nunca utilizamos un framework reactivo es, justamente, ajustarnos a ese nuevo paradigma. Como Vue es muy flexible, es posible que al abordarlo acostumbrados, por ejemplo, a jQuery, lo forcemos a funcionar de una manera que no se ajuste demasiado a lo que sus diseñadores y el paradigma reactivo establecen. En este artículo vamos a ver cómo encarar la comunicación entre componentes, que implica dos elementos fundamentales: props y eventos.
Paradigma reactivo
Antes de adentrarnos en el tema de los props y los eventos, vamos a ver una definición básica sobre el paradigma reactivo. Según el Reactive Manifesto, los sistemas reactivos son:
- responsivos. Aseguran la calidad del servicio cumpliendo unos tiempos de respuesta establecidos;
- resilientes. Se mantienen responsivos incluso cuando se enfrentan a situaciones de error;
- elásticos. Se mantienen responsivos incluso ante aumentos en la carga de trabajo;
- orientados a mensajes. Minimizan el acoplamiento entre componentes al establecer interacciones basadas en el intercambio de mensajes de manera asíncrona.
El problema que viene a solucionar el paradigma reactivo, entonces, tiene que ver con las limitaciones de escalamiento de las soluciones tradicionales, en particular en lo relativo a la paralelización de las peticiones y a la composición de las plantillas web.
Una diferencia fundamental entre una solución tradicional y una reactiva es que la segunda se basa en el concepto de push en lugar del concepto de pull. Por ejemplo, si tenemos un componente que muestra la cantidad de personas conectadas a un sitio, en lugar de hacer que este “pregunte” cada tanto la cantidad de gente conectada para renderizar el resultado (método pull), las soluciones reactivas basan su funcionamiento en que cualquier cambio en ese número genere un envío del nuevo dato al componente de renderizado para su actualización (método push).
Veamos un ejemplo muy sencillo en Javascript que nos va dar una idea muy clara de la diferencia entre el método pull, típico de la programación imperativa, y el método push, propio del enfoque reactivo:
Pull (imperativo)
var val1 = 10;
var val2 = 20;
var sum = val1 + val2;
console.log(sum); // Imprime: 30
val1 = 5;
console.log(sum); // Imprime: 30
Push (reactivo)
var val1 = 10;
var val2 = 20;
var sum = val1 + val2;
console.log(sum); // Imprime: 30
val1 = 5;
console.log(sum); // Imprime: 25
Entender los props
De los dos conceptos mencionados en la introducción, el de los props es el más extraño para quien nunca usó un paradigma reactivo. En su nivel más básico, un prop es una variable que un componente “padre” (contenedor) le pasa a un componente “hijo” (contenido). El prop es potestad del componente contenedor, por lo que el componente contenido, que lo recibe, no puede modificarlo, solo leer su valor.
Si lo vinculamos con lo que vimos sobre enfoque push, el prop es el valor reactivo que, al cambiar, suscita que todos los componentes que lo lean o utilicen actualicen sus plantillas o los resultados de sus funciones.
Pero ¿por qué no se permite que los componentes contenidos lo modifiquen? Según el ideal de Vue, los componentes de renderizado deben ser “bobos”, sin ninguna lógica de negocio. Lo ideal es que haya otros componentes que se encarguen de realizar los cálculos de negocio. Los resultados de dichos cálculos se almacenarán en variables que se pasarán como props a los componentes contenidos de renderizado, o a otros componentes de negocio que utilicen esos valores en sus propios cálculos. De esta manera, podemos ir armando una suerte de reacción en cadena cuando cambia un prop.
¿Y los eventos?
Acabamos de ver que los componentes que reciben los props no pueden modificarlos. Pero entonces, ¿cómo puede funcionar el ejemplo del artículo anterior, donde tenemos un botón con un caption numérico que va incrementándose a medida que hacemos clic? Si ese número lo recibe como prop, ¿cómo puede el click del botón modificar el número, si no puede sobrescribirlo?
En este punto entra la otra parte de la ecuación. Hasta aquí, vimos únicamente un flujo de comunicación descendente: los componentes contenedores envían sus props en sentido descendente hacia sus componentes contenidos. Pero también tenemos un flujo de comunicación ascendente, dado por los eventos.
La respuesta a la pregunta, entonces, es que el componente contenido no necesita modificar el prop, sino avisarle al componente contenedor que ese número debe cambiar.
Vamos a analizar un ejemplo sencillo para ver cómo funcionan juntos los props y los eventos. Imaginemos que tenemos un componente contenedor con una variable de estado llamada contador, y un componente contenido que representa un botón con ese contador escrito en él. Al hacer click en el botón, el contador se incrementa.
Componente contenedor (ContenedorEjemplo)
<template>
<h1>Ejemplo de props y eventos</h1>
<boton-ejemplo :contador="contador" @evento-incrementar-contador="incrementarContador"></boton-ejemplo>
</template>
<script>
import BotonEjemplo from "./BotonEjemplo.vue";
export default {
name: "ContenedorEjemplo",
data() {
return {
contador: 0 //Valor inicial
}
},
methods: {
incrementarContador: function () {
this.contador++;
},
components: {
BotonEjemplo
}
}
</script>
Componente contenido (BotonEjemplo)
<template>
<button @click="emitirEventoIncrementarContador">Ya hiciste {{ contador }} clicks</button>
</template>
<script>
export default {
name: "BotonEjemplo",
props: {
contador: Number
},
methods: {
emitirEventoIncrementarContador: function () {
this.$emit("evento-incrementar-contador");
}
}
</script>
Y lo que ocurre al hacer click es lo siguiente:
- BotonEjemplo tiene asociado el evento onclick del botón de su template con su propio método “emitirEventoIncrementarContador” (@click="emitirEventoIncrementarContador").
- Por eso, al hacer click en el botón, se ejecuta dicho método de BotonEjemplo, que emite el evento “evento-incrementar-contador”.
- Como los eventos se disparan en sentido ascendente, ContenedorEjemplo recibe automáticamente la señal del evento.
- ContenedorEjemplo tiene asociado ese evento a su método incrementarContador() (@evento-incrementar-contador="incrementarContador"), por lo que lo ejecuta.
- El método incrementarContador() incrementa la variable contador en 1. ContenedorEjemplo tiene permiso para hacerlo ya que para él, contador no es un prop sino un data.
- Como la variable contador es un prop de BotonEjemplo, y este lo usa en su plantilla HTML (Ya hiciste {{ contador }} clicks), su modificación desata una nueva renderización de la plantilla, más en particular de la parte de la plantilla que utiliza el prop.
- Como resultado de esta nueva renderización, vemos en pantalla el contador del botón actualizado.
En resumen, ocurre algo muy simple: BotonEjemplo envía un mensaje ascendente al componente contenedor (ContenedorEjemplo), y luego se actualiza una prop, lo cual genera un mensaje descendente que envía el nuevo valor (siguiendo el concepto de push) a BotonEjemplo.
Como podemos apreciar, este paradigma genera un gran desacople entre los componentes. BotonEjemplo no sabe nada de ContenedorEjemplo, y este último solo conoce al primero porque lo importa y lo utiliza en su template. Uno envía un evento y recibe cambios en sus props, y el otro escucha un evento y ejecuta métodos. Son fácilmente reemplazables. Este es el poder del paradigma reactivo.
Llegamos al final de este artículo, esperamos que sea claro y los ayude a empezar a adentrarse en la manera “Vue” de hacer las cosas. Ante cualquier duda o sugerencia, no duden en dejar un comentario.
¡Saludos, y nos vemos en la próxima edición!