Fundamentos de Reactividad
Preferencias de API
Esta página y muchos otros capítulos posteriores en la guía tienen contenido diferente para la Options API y la Composition API. Tu preferencia actual es Composition API. Puedes alternar entre los estilos de API usando el interruptor de "Preferencia de API" en la parte superior de la barra lateral izquierda.
Declarando Estado Reactivo
### ref()
**
En la Composition API, la manera recomendada de declarar estado reactivo es usando la función ref()
:
js
import { ref } from 'vue'
const count = ref(0)
ref()
toma el argumento y lo devuelve empaquetado en un objeto ref con la propiedad .value
:
js
const count = ref(0)
console.log(count) // { value : 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
Consulta también: Escritura de Refs
Para usar refs en la plantilla de un componente, decláralos y devuélvelos desde la función setup()
del componente:
js
import { ref } from 'vue'
export default {
// `setup` es un hook especial dedicado para la Composition API.
setup() {
const count = ref(0})
// expone la ref a la plantilla
return {
count
}
}
}
template
<div>{{ count }}</div>
Nota que no necesitamos agregar .value
al usar la ref en la plantilla. Por conveniencia, las refs son desenvueltos automáticamente al ser usadas dentro de plantillas (con algunas advertencias).
También puedes mutar una ref directamente en manejadores de eventos:
template
<button @click="count++">
{{ count }}
</button>
Para lógica más compleja, podemos declarar funciones que mutan refs en el mismo ámbito y exponerlas como métodos junto con el estado:
js
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
function increment() {
// .value es necesario en JavaScript
count.count++
}
// no te olvides de exponer la función también.
return {
count,
increment
}
}
}
Los métodos expuestos pueden después ser usados como manejadores de eventos:
template
<button @click="increment">
{{ count }}
</button>
Acá hay un ejemplo en CodePen, sin usar herramientas de compilación.
<script setup>
Exponer manualmente el estado y los métodos a través de setup()
puede ser verboso. Por suerte, se puede evitar cuando se utilizan Componentes de un Solo Archivo (SFC). Podemos simplificar el uso con <script setup>
:
vue
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">
{{ count }}
</button>
</template>
Pruébalo en la Zona de Práctica
Las importaciones de nivel superior, variables y funciones declaradas en el <script setup>
se pueden usar automáticamente en la plantilla del mismo componente. Piensa la plantilla como una funcion de JavaScript declarada en el mismo ámbito, naturalmente tiene acceso a todo lo declarado en conjunto.
TIP
Para el resto de la guía, utilizaremos principalmente la sintaxis SFC + <script setup>
para los ejemplos de código de la Composition API, ya que ese es el uso más común para los desarrolladores de Vue.
Si no estas usando SFC, puedes seguir usando la Composition API con la opción setup()
.
¿Por qué Refs?
Te puedes estar preguntando la razón de necesitar refs con el .value
en vez de variables simples. Para explicar eso, necesitamos discutir brevemente cómo funciona el sistema de reactividad de Vue.
Cuando usas una ref en la plantilla, y después cambias el valor de la ref, Vue detecta el cambio automáticamente y actualiza el DOM como corresponda. Esto es posible con un sistema de reactividad basado en el seguimiento de dependencias. Cuando un componente es renderizado por primera vez, Vue rastrea cada ref que fue usada durante la renderización. Luego, cuando alguna ref mute, activará una nueva renderización de los componentes que la están rastreando.
En JavaScript estándar, no hay una manera de detectar el acceso o mutación de variables simples. Sin embargo, podemos interceptar las operaciones get y set de las propiedades del objeto usando métodos getter y setter.
La propiedad .value
le da a Vue la oportunidad de detectar cuando una ref ha sido accedida o mutada. Bajo la superficie, Vue realiza el seguimiento en su getter, y activa en su setter. Conceptualmente, puedes pensar en una ref como un objeto que se ve así:
js
// pseudocódigo, no la implementación real
const myRef = {
_value: 0,
get value() {
track()
return this._value
},
set value(newValue) {
this._value = newValue
trigger()
}
}
Otra buena característica de las refs es que a diferencia de variables simples, puedes pasar las refs a funciones reteniendo el acceso a su último valor y la conexión de reactividad. Esto es particularmente útil al refactorizar lógica compleja en código reutilizable.
El sistema de reactividad es discutido en más detalle en la sección Reactividad en Profundidad.
Reactividad profunda
Las refs pueden tener cualquier tipo de valor, incluyendo objetos profundamente anidados, arrays, o estructuras que son parte de JavaScript como Map
.
Una ref hará que su valor sea profundamente reactivo. Esto significa que puedes esperar que se detecten cambios aún cuando mutas objetos o arrays anidados.
js
import { ref } from 'vue'
const obj = ref({
nested: { count: 0 },
arr: ['foo', 'bar']
})
function mutateDeeply() {
// estos funcionarán como se esperaba.
obj.value.nested.count++
obj.value.arr.push('baz')
}
Valores no primitivos son convertidos en proxies reactivos mediante reactive()
, lo que es discutido más abajo.
También es posible no optar por reactividad profunda con refs superficiales. Para refs superficiales, sólo se registra el acceso a .value para la reactividad. Las refs superficiales pueden ser usadas para optimizar rendimiento al evitar los costos de observación de objetos grandes, o en casos donde el estado interno es manejado por una librería externa.
Véase también
Tiempo de Actualización del DOM
Cuando mutas el estado reactivo, el DOM se actualiza automáticamente. Sin embargo, hay que tener en cuenta que las actualizaciones del DOM no se aplican de forma sincrónica. En su lugar, Vue las almacena en búfer hasta la "siguiente marca" (next tick) del ciclo de actualización para garantizar que cada componente tenga que actualizarse sólo una vez, independientemente de cuántos cambios de estado hayas realizado.
Para esperar a que se complete la actualización del DOM después de un cambio de estado, puedes usar la API global nextTick():
js
import { nextTick } from 'vue'
async function increment() {
count.value++
await nextTick(() => {
// acceder al DOM actualizado
})
}
## reactive()
**
Existe otra forma de declarar estado reactivo, con la API reactive()
. A diferencia de una ref, que envuelve el valor interno en un objeto especial, reactive()
hace reactivo al objeto mismo:
js
import { reactive } from 'vue'
const state = reactive({ count: 0 })
Consulta también: Escritura de
reactive()
Uso en plantillas:
template
<button @click="state.count++">
{{ state.count }}
</button>
Los objetos reactivos son proxies de JavaScript y se comportan como objetos normales. La diferencia es que Vue es capaz de interceptar el acceso y la mutación de todas las propiedades de un objeto reactivo para el seguimiento y activación de la reactividad.
reactive()
convierte profundamente los objetos: objetos anidados también son envueltos con reactive()
al ser accedidos. También es llamado por ref()
internamente cuando el valor de la ref es un objeto. Similar a las refs superficiales, también está la API shallowReactive()
para no optar por la reactividad profunda.
Proxy Reactivo vs. Original
Es importante tener en cuenta que el valor devuelto por reactive()
es un Proxy del objeto original, que no es igual al objeto original:
js
const raw = {}
const proxy = reactive(raw)
// el proxy NO es igual al original.
console.log(proxy === raw) // false
Solo el proxy es reactivo: la mutación del objeto original no activará las actualizaciones. Por lo tanto, la mejor práctica cuando se trabaja con el sistema de reactividad de Vue es utilizar exclusivamente las versiones proxy de tu estado.
Para asegurar un acceso consistente al proxy, llamar a reactive()
en el mismo objeto siempre devuelve el mismo proxy, y llamar a reactive()
en un proxy existente también devuelve ese mismo proxy:
js
// llamar a reactive () en el mismo objeto devuelve el mismo proxy
console.log(reactive(raw) === proxy) // true
// llamar a reactive() en un proxy devuelve ese mismo proxy
console.log(reactive(proxy) === proxy) // true
Esta regla también se aplica a los objetos anidados. Debido a la reactividad profunda, los objetos anidados dentro de un objeto reactivo también son proxies:
js
const proxy = reactive({})
const raw = {}
proxy.nested = raw
console.log(proxy.nested === raw) // false
Limitaciones de reactive()
La API reactive()
tiene algunas limitaciones:
Tipos de valores limitados: esta solo funciona para tipos de objetos (objetos, arrays y objetos globales como
Map
ySet
). No puede contener tipos primitivos comostring
,number
oboolean
.No puede reemplazar un objeto entero: dado que el seguimiento de la reactividad de Vue funciona sobre el acceso a la propiedad, siempre debemos mantener la misma referencia al objeto reactivo. Esto significa que no podemos " reemplazar" fácilmente un objeto reactivo porque se pierde la conexión de la reactividad con la primera referencia:
jslet state = reactive({ count: 0 }) // la referencia de arriba ({ count: 0 }) ya no está siendo seguida // (se pierde la conexión de reactividad) state = reactive({ count: 1 })
No es amigable con la desestructuración: cuando desestructuramos una propiedad de tipo primitivo de un objeto reactivo en variables locales, o cuando pasamos esa propiedad a una función, perderemos la conexión de reactividad:
jsconst state = reactive({ count: 0 }) // count está desconectado de state.count cuando es desestructurado let { count } = state // no afecta el estado original count++ // la función recibe un número simple y no será capaz de // realizar un seguimiento de los cambios en state.count // tenemos que pasar el objeto completo para retener reactividad callSomeFunction(state.count)
Debido a estas limitaciones, recomendamos usar ref()
como la API principal para declarar estado reactivo.
Detalles adicionales sobre desempaquetado de refs
Como propiedad de un objeto reactivo
Una ref es automaticamente desempaquetada cuando es accedida o mutada como propiedad de un objeto reactivo. En otras palabras, se comporta como una propiedad normal:
js
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1
Si se asigna una nueva ref a una propiedad vinculada a una ref existente, ésta sustituirá a la antigua ref:
js
const otherCount = ref(2)
state.count = otherCount
console.log(state.count) // 2
// la ref original ahora está desconectada de state.count
console.log(count.value) // 1
El desempaquetado de la ref sólo ocurre cuando se anida dentro de un objeto reactivo profundo. No se aplica cuando se accede como una propiedad de un objeto reactivo superficial.
### Advertencias en Arrays y Collecciones **
A diferencia de los objetos reactivos, no se realiza ningún desempaquetado cuando se accede a la ref como elemento de un array reactivo o de un tipo de colección nativa como Map
js
const books = reactive([ref('Vue 3 Guide')])
// necesita .value aquí
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// necesita .value aquí
console.log(map.get('count').value)
Advertencia al Desempaquetar en Plantillas
Desempaquetar a refs en plantillas solo aplica si la ref es una propiedad de nivel superior en el contexto del renderizado de la plantilla.
En el siguiente ejemplo, count
y object
son propiedades de nivel superior, pero object.id
no lo es:
js
const count = ref(0)
const object = { id: ref(1) }
Por lo tanto, esta expresión funciona como es esperado:
template
{{ count + 1 }}
...mientras que esto NO:
template
{{ object.id + 1}}
El resultado de la renderización será [object Object]1
porque object.id
no está desempaquetado al evaluar la expresión y sigue siendo un objeto ref. Para arreglar podemos desestructurar id
para que sea una propiedad de nivel superior:
js
const { id } = object
template
{{ id + 1 }}
Ahora el resultado del renderizado será 2
.
Otra cosa que hay que tener en cuenta es que una ref se desempaquetará si es el valor final evaluado de una interpolación de texto (es decir, una etiqueta {{ }}
), por lo que lo siguiente renderizará 1
:
template
{{ object.id }}
Esto es una característica conveniente de interpolación de texto y es equivalente a {{ object.id.value }}
.