Reactividad en Vue
Publicado: 2022-03-10En este artículo, veremos la reactividad en Vue, cómo funciona y cómo podemos crear variables reactivas utilizando métodos y funciones recién creados. Este artículo está dirigido a desarrolladores que conocen bien cómo funciona Vue 2.x y buscan familiarizarse con el nuevo Vue 3.
Vamos a construir una aplicación simple para comprender mejor este tema. El código de esta aplicación se puede encontrar en GitHub.
De forma predeterminada, JavaScript no es reactivo . Esto quiere decir que si creamos la variable boy
y la referenciamos en la parte A de nuestra aplicación, luego procedemos a modificar boy
en la parte B, la parte A no se actualizará con el nuevo valor de boy
.
let framework = 'Vue'; let sentence = `${framework} is awesome`; console.log(sentence) // logs "Vue is awesome" framework = 'React'; console.log(sentence) //should log "React is awesome" if 'sentence' is reactive.
El fragmento anterior es un ejemplo perfecto de la naturaleza no reactiva de JavaScript; por lo tanto, el cambio no se refleja en la variable de sentence
.
En Vue 2.x, props
, computed
y data()
eran todos reactivos de forma predeterminada, con la excepción de las propiedades que no están presentes en data
cuando se crean dichos componentes. Esto significa que cuando se inyecta un componente en el DOM, solo las propiedades existentes en el objeto de data
del componente harían que el componente se actualice si tales propiedades cambian.
Internamente, Vue 3 usa el objeto Proxy
(una característica de ECMAScript 6) para garantizar que estas propiedades sean reactivas, pero aún ofrece la opción de usar Object.defineProperty
de Vue 2 para la compatibilidad con Internet Explorer (ECMAScript 5). Este método define una nueva propiedad directamente en un objeto o modifica una propiedad existente en un objeto y devuelve el objeto.
A primera vista y dado que la mayoría de nosotros ya sabemos que la reactividad no es nueva en Vue, puede parecer innecesario hacer uso de estas propiedades, pero la API de opciones tiene sus limitaciones cuando se trata de una aplicación grande con funciones reutilizables en varios partes de la aplicación. Con este fin, se introdujo la nueva API de composición para ayudar con la lógica de abstracción a fin de facilitar la lectura y el mantenimiento de una base de código. Además, ahora podemos hacer que cualquier variable sea fácilmente reactiva, independientemente de su tipo de datos, utilizando cualquiera de las nuevas propiedades y métodos.
Cuando usamos la opción de setup
, que sirve como punto de entrada para la API de composición, el objeto data
, las propiedades computed
y los methods
son inaccesibles porque la instancia del componente aún no se ha creado cuando se ejecuta setup
. Esto hace que sea imposible aprovechar la reactividad integrada en cualquiera de estas funciones en setup
. En este tutorial, vamos a aprender acerca de todas las formas en que podemos hacer esto.
El método reactivo
De acuerdo con la documentación, el método reactive
, que es el equivalente de Vue.observable()
en Vue 2.6, puede ser útil cuando intentamos crear un objeto cuyas propiedades son todas reactivas (como el objeto de data
en Opciones API). Bajo el capó, el objeto de data
en la API de opciones usa este método para hacer que todas las propiedades sean reactivas.
Pero podemos crear nuestro propio objeto reactivo como este:
import { reactive } from 'vue' // reactive state let user = reactive({ "id": 1, "name": "Leanne Graham", "username": "Bret", "email": "[email protected]", "address": { "street": "Kulas Light", "suite": "Apt. 556", "city": "Gwenborough", "zipcode": "92998-3874", "geo": { "lat": "-37.3159", "lng": "81.1496" } }, "phone": "1-770-736-8031 x56442", "website": "hildegard.org", "company": { "name": "Romaguera-Crona", "catchPhrase": "Multi-layered client-server neural-net", "bs": "harness real-time e-markets" }, "cars": { "number": 0 } })
Aquí, importamos el método reactive
de Vue y luego declaramos nuestra variable de user
pasando su valor a esta función como argumento. Al hacerlo, hemos hecho que el user
sea reactivo y, por lo tanto, si usamos user
en nuestra plantilla y si el objeto o una propiedad de este objeto cambia, entonces este valor se actualizará automáticamente en esta plantilla.
ref
Así como tenemos un método para hacer que los objetos sean reactivos, también necesitamos uno para hacer que otros valores primitivos independientes (cadenas, valores booleanos, valores indefinidos, números, etc.) y matrices sean reactivos. Durante el desarrollo, trabajaríamos con estos otros tipos de datos y al mismo tiempo necesitaríamos que fueran reactivos. El primer enfoque en el que podríamos pensar sería usar reactive
y pasar el valor de la variable que queremos hacer reactivo.
import { reactive } from 'vue' const state = reactive({ users: [], });
Debido a que reactive
tiene una conversión reactiva profunda, user
como propiedad también sería reactivo, logrando así nuestro objetivo; por lo tanto, user
siempre actualizaría en cualquier lugar donde se use en la plantilla de dicha aplicación. Pero con la propiedad ref
, podemos hacer que cualquier variable con cualquier tipo de datos sea reactiva pasando el valor de esa variable a ref
. Este método también funciona para objetos, pero anida el objeto un nivel más profundo que cuando se usa el método reactive
.
let property = { rooms: '4 rooms', garage: true, swimmingPool: false } let reactiveProperty = ref(property) console.log(reactiveProperty) // prints { // value: {rooms: "4 rooms", garage: true, swimmingPool: false} // }
Bajo el capó, ref
toma este argumento que se le pasa y lo convierte en un objeto con una clave de value
. Esto significa que podemos acceder a nuestra variable llamando a variable.value
, y también podemos modificar su valor llamándolo de la misma manera.
import {ref} from 'vue' let age = ref(1) console.log(age.value) //prints 1 age.value++ console.log(age.value) //prints 2
Con esto, podemos importar ref
a nuestro componente y crear una variable reactiva:
<template> <div class="home"> <form @click.prevent=""> <table> <tr> <th>Name</th> <th>Username</th> <th>email</th> <th>Edit Cars</th> <th>Cars</th> </tr> <tr v-for="user in users" :key="user.id"> <td>{{ user.name }}</td> <td>{{ user.username }}</td> <td>{{ user.email }}</td> <td> <input type="number" name="cars" v-model.number="user.cars.number" /> </td> <td> <cars-number :cars="user.cars" /> </td> </tr> </table> <p>Total number of cars: {{ getTotalCars }}</p> </form> </div> </template> <script> // @ is an alias to /src import carsNumber from "@/components/cars-number.vue"; import axios from "axios"; import { ref } from "vue"; export default { name: "Home", data() { return {}; }, setup() { let users = ref([]); const getUsers = async () => { let { data } = await axios({ url: "data.json", }); users.value = data; }; return { users, getUsers, }; }, components: { carsNumber, }, created() { this.getUsers(); }, computed: { getTotalCars() { let users = this.users; let totalCars = users.reduce(function(sum, elem) { return sum + elem.cars.number; }, 0); return totalCars; }, }; </script>
Aquí, importamos ref
para crear una variable de users
reactivos en nuestro componente. Luego axios
para obtener datos de un archivo JSON en la carpeta public
e importamos nuestro componente carsNumber
, que crearemos más adelante. Lo siguiente que hicimos fue crear una variable de users
reactivos usando el método ref
, para que users
puedan actualizar cada vez que cambie la respuesta de nuestro archivo JSON.
También creamos una función getUser
que obtiene la matriz de users
de nuestro archivo JSON usando axios, y asignamos el valor de esta solicitud a la variable de users
. Finalmente, creamos una propiedad computada que calcula la cantidad total de autos que tienen nuestros usuarios, ya que la hemos modificado en la sección de plantillas.
Es importante tener en cuenta que al acceder a las propiedades de ref
que se devuelven en la sección de plantilla o fuera de setup()
, se desenvuelven automáticamente de forma superficial. Esto significa que las refs
que son un objeto aún requerirían un .value
. para poder acceder. Debido a que users
es una matriz, podríamos simplemente usar users
y no getTotalCars
users.value
En la sección de plantillas, mostramos una tabla que muestra la información de cada usuario, junto con un componente <cars-number />
. Este componente acepta un accesorio de cars
que se muestra en la fila de cada usuario como la cantidad de autos que tienen. Este valor se actualiza cada vez que cambia el valor de los cars
en el objeto de usuario , que es exactamente cómo funcionaría el objeto data
o la propiedad computed
si estuviéramos trabajando con la API de opciones.
toRefs
Cuando usamos la API de Composición, la función de setup
acepta dos argumentos: props
y context
. Estos props
se pasan del componente a setup()
, y hace posible acceder a los accesorios que tiene el componente desde dentro de esta nueva API. Este método es particularmente útil porque permite desestructurar objetos sin perder su reactividad.
<template> <p>{{ cars.number }}</p> </template> <script> export default { props: { cars: { type: Object, required: true, }, gender: { type: String, required: true, }, }, setup(props) { console.log(props); // prints {gender: "female", cars: Proxy} }, }; </script> <style></style>
Para usar un valor que es un objeto de props
en la API de composición mientras nos aseguramos de que mantenga su reactividad, usamos toRefs
. Este método toma un objeto reactivo y lo convierte en un objeto simple en el que cada propiedad del objeto reactivo original se convierte en una ref
. Lo que esto significa es que los cars
prop...
cars: { number: 0 }
… ahora se convertiría en esto:
{ value: cars: { number: 0 }
Con esto, podemos hacer uso de cars
dentro de cualquier parte de la API de configuración mientras mantenemos su reactividad.
setup(props) { let { cars } = toRefs(props); console.log(cars.value); // prints {number: 0} },
Podemos ver esta nueva variable usando el watch
de la API de Composición y reaccionar a este cambio como queramos.
setup(props) { let { cars } = toRefs(props); watch( () => cars, (cars, prevCars) => { console.log("deep ", cars.value, prevCars.value); }, { deep: true } ); }
toRef
Otro caso de uso común que podríamos enfrentar es pasar un valor que no es necesariamente un objeto, sino uno de los tipos de datos que funcionan con ref
(matriz, número, cadena, booleano, etc.). Con toRef
, podemos crear una propiedad reactiva (es decir, ref
) a partir de un objeto reactivo de origen. Hacer esto garantizaría que la propiedad permanezca reactiva y se actualice cada vez que cambie la fuente principal.
const cars = reactive({ Toyota: 1, Honda: 0 }) const NumberOfHondas = toRef(state, 'Honda') NumberOfHondas.value++ console.log(state.Honda) // 1 state.Honda++ console.log(NumberOfHondas.value) // 2
Aquí, creamos un objeto reactivo usando el método reactive
, con las propiedades Toyota
y Honda
. También usamos toRef
para crear una variable reactiva a partir de Honda
. En el ejemplo anterior, podemos ver que cuando actualizamos Honda
usando el objeto de cars
reactivos o NumberOfHondas
, el valor se actualiza en ambos casos.
Este método es similar y, sin embargo, muy diferente del método toRefs
que cubrimos anteriormente en el sentido de que mantiene su conexión con su fuente y puede usarse para cadenas, matrices y números. A diferencia de toRefs
, no necesitamos preocuparnos por la existencia de la propiedad en su origen en el momento de la creación, porque si esta propiedad no existe en el momento en que se crea esta ref
y en su lugar devuelve null
, aún estaría almacenada como una propiedad válida, con una forma de watcher
establecida, de modo que cuando este valor cambie, esta ref
creada usando toRef
también se actualizará.
También podemos usar este método para crear una propiedad reactiva a partir de props
. Eso se vería así:
<template> <p>{{ cars.number }}</p> </template> <script> import { watch, toRefs, toRef } from "vue"; export default { props: { cars: { type: Object, required: true, }, gender: { type: String, required: true, }, }, setup(props) { let { cars } = toRefs(props); let gender = toRef(props, "gender"); console.log(gender.value); watch( () => cars, (cars, prevCars) => { console.log("deep ", cars.value, prevCars.value); }, { deep: true } ); }, }; </script>
Aquí, creamos una ref
que se basaría en la propiedad de gender
obtenida de props
. Esto es útil cuando queremos realizar operaciones adicionales en el accesorio de un componente en particular.
Conclusión
En este artículo, hemos analizado cómo funciona la reactividad en Vue utilizando algunos de los métodos y funciones recientemente introducidos de Vue 3. Comenzamos observando qué es la reactividad y cómo Vue utiliza el objeto Proxy
detrás de escena para lograr esto. También vimos cómo podemos crear objetos reactivos usando reactive
y cómo crear propiedades reactivas usando ref
.
Finalmente, vimos cómo convertir objetos reactivos en objetos simples, cada una de cuyas propiedades es una ref
que apunta a la propiedad correspondiente del objeto original, y vimos cómo crear una ref
para una propiedad en un objeto fuente reactivo.
Más recursos
- “Proxy” (objeto), MDN Web Docs
- “Fundamentos de reactividad”, Vue.js
- "Referencias", Vue.js
- “
setup
interna de registro de gancho de ciclo de vida”, Vue.js