Fundamentos de Reactividad
Preferencia de API
Esta página y muchos otros capítulos posteriores de la guía contienen contenido diferente para la Options API y la Composition API. Tu preferencia actual es Composition API. Puedes alternar entre los estilos de API usando los selectores de "Preferencia de API" en la parte superior de la barra lateral izquierda.
Declarando el Estado Reactivo
ref()
En la Composition API, la forma recomendada de declarar el estado reactivo es usando la función ref():
js
import { ref } from 'vue'
const count = ref(0)ref() toma el argumento y lo retorna envuelto dentro de un objeto ref con una propiedad .value:
js
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1Ver también: Tipado de Refs
Para acceder a las refs en el template de un componente, decláralas y retórnalas 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 el ref al template
return {
count
}
}
}template
<div>{{ count }}</div>Observa que no necesitamos añadir .value al usar la ref en el template. Por comodidad, las refs se desenvuelven automáticamente cuando se usan dentro de templates (con algunas advertencias).
También puedes mutar una ref directamente en los manejadores de eventos:
template
<button @click="count++">
{{ count }}
</button>Para una 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.value++
}
// no olvides exponer también la función.
return {
count,
increment
}
}
}Los métodos expuestos pueden luego usarse como manejadores de eventos:
template
<button @click="increment">
{{ count }}
</button>Aquí tienes el ejemplo en vivo en Codepen, sin usar ninguna herramienta de compilación.
<script setup>
Exponer manualmente el estado y los métodos a través de setup() puede ser tedioso. Afortunadamente, se puede evitar al usar Componentes de Archivo Único (SFCs). 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>Las importaciones de nivel superior, variables y funciones declaradas en <script setup> son automáticamente utilizables en el template del mismo componente. Piensa en el template como una función de JavaScript declarada en el mismo ámbito: naturalmente tiene acceso a todo lo declarado junto a ella.
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 es el uso más común para los desarrolladores de Vue.
Si no estás utilizando SFC, aún puedes usar la Composition API con la opción setup().
¿Por qué Refs?
Puede que te estés preguntando por qué necesitamos refs con el .value en lugar de variables simples. Para explicar eso, tendremos que discutir brevemente cómo funciona el sistema de reactividad de Vue.
Cuando usas una ref en un template, y cambias el valor de la ref más tarde, Vue detecta automáticamente el cambio y actualiza el DOM en consecuencia. Esto es posible gracias a un sistema de reactividad basado en el seguimiento de dependencias. Cuando un componente se renderiza por primera vez, Vue rastrea cada ref que se usó durante la renderización. Más tarde, cuando una ref es mutada, activará una nueva renderización para los componentes que la están rastreando.
En JavaScript estándar, no hay forma de detectar el acceso o la mutación de variables simples. Sin embargo, podemos interceptar las operaciones de obtención y establecimiento de las propiedades de un objeto usando métodos getter y setter.
La propiedad .value le da a Vue la oportunidad de detectar cuándo se ha accedido o mutado a una ref. Internamente, Vue realiza el seguimiento en su getter y la activación en su setter. Conceptualmente, puedes pensar en una ref como un objeto que se ve así:
js
// pseudocódigo, no implementación real
const myRef = {
_value: 0,
get value() {
track()
return this._value
},
set value(newValue) {
this._value = newValue
trigger()
}
}Otra característica interesante de las refs es que, a diferencia de las variables simples, puedes pasar refs a funciones mientras mantienes el acceso al ú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 se discute con más detalle en la sección Reactividad en Profundidad.
Reactividad Profunda
Las Refs pueden contener cualquier tipo de valor, incluyendo objetos profundamente anidados, arrays, o estructuras de datos incorporadas de JavaScript como Map.
Una ref hará que su valor sea profundamente reactivo. Esto significa que puedes esperar que los cambios sean detectados incluso cuando mutas objetos o arrays anidados:
js
import { ref } from 'vue'
const obj = ref({
nested: { count: 0 },
arr: ['foo', 'bar']
})
function mutateDeeply() {
// funcionarán como se espera.
obj.value.nested.count++
obj.value.arr.push('baz')
}Los valores no primitivos se convierten en proxies reactivos a través de reactive(), lo cual se discute a continuación.
También es posible optar por no usar la reactividad profunda con shallow refs. Para las shallow refs, solo el acceso a .value se rastrea para la reactividad. Las shallow refs pueden usarse para optimizar el rendimiento evitando el costo de observación de objetos grandes, o en casos donde el estado interno es gestionado por una librería externa.
Lectura adicional:
Momento de Actualización del DOM
Cuando mutas el estado reactivo, el DOM se actualiza automáticamente. Sin embargo, debe tenerse en cuenta que las actualizaciones del DOM no se aplican de forma síncrona. En su lugar, Vue las almacena en un búfer hasta la "siguiente iteración" (next tick) en el ciclo de actualización para asegurar que cada componente se actualice solo una vez, sin importar cuántos cambios de estado hayas realizado.
Para esperar a que la actualización del DOM se complete 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()
// Ahora el DOM está actualizado
}reactive()
Hay otra forma de declarar el estado reactivo, con la API reactive(). A diferencia de una ref que envuelve el valor interno en un objeto especial, reactive() hace que un objeto sea reactivo en sí mismo:
js
import { reactive } from 'vue'
const state = reactive({ count: 0 })Ver también: Tipado de Reactive
Uso en template:
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 la activación de la reactividad.
reactive() convierte el objeto profundamente: los objetos anidados también se envuelven con reactive() cuando se accede a ellos. También es llamada por ref() internamente cuando el valor de la ref es un objeto. Similar a las shallow refs, también existe la API shallowReactive() para optar por no usar la reactividad profunda.
Proxy Reactivo vs. Original
Es importante notar que el valor retornado de 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) // falseSolo el proxy es reactivo - mutar el objeto original no activará actualizaciones. Por lo tanto, la mejor práctica al trabajar con el sistema de reactividad de Vue es usar exclusivamente las versiones proxy de tu estado.
Para asegurar un acceso consistente al proxy, llamar a reactive() sobre el mismo objeto siempre retorna el mismo proxy, y llamar a reactive() sobre un proxy existente también retorna ese mismo proxy:
js
// llamar a reactive() sobre un mismo objeto retorna el mismo proxy
console.log(reactive(raw) === proxy) // true
// llamar a reactive() sobre un proxy lo retorna a sí mismo
console.log(reactive(proxy) === proxy) // trueEsta 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) // falseLimitaciones de reactive()
La API reactive() tiene algunas limitaciones:
Tipos de valor limitados: solo funciona para tipos de objeto (objetos, arrays y tipos de colección como
MapySet). No puede contener tipos primitivos comostring,numberoboolean.No se puede reemplazar el objeto completo: dado que el seguimiento de reactividad de Vue funciona sobre el acceso a propiedades, siempre debemos mantener la misma referencia al objeto reactivo. Esto significa que no podemos "reemplazar" fácilmente un objeto reactivo porque la conexión de reactividad con la primera referencia se pierde:
jslet state = reactive({ count: 0 }) // la referencia anterior ({ count: 0 }) ya no se rastrea // (¡se ha perdido la conexión de reactividad!) state = reactive({ count: 1 })No es amigable con la desestructuración: cuando desestructuramos la 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 se desconecta de state.count cuando se desestructura. let { count } = state // no afecta el estado original count++ // La función recibe un número simple y no // podrá rastrear los cambios en state.count. // Tenemos que pasar el objeto completo para mantener la reactividad. callSomeFunction(state.count)
Debido a estas limitaciones, recomendamos usar ref() como la API principal para declarar el estado reactivo.
Detalles Adicionales del Desenvolvimiento de Refs
Como Propiedad de Objeto Reactivo
Una ref se desenvuelve automáticamente cuando se accede o se muta como una 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) // 1Si una nueva ref se asigna a una propiedad vinculada a una ref existente, reemplazará a la ref antigua:
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) // 1El desenvolvimiento de refs solo ocurre cuando está anidado dentro de un objeto reactivo profundo. No se aplica cuando se accede como una propiedad de un objeto reactivo superficial.
Advertencia en Arrays y Colecciones
A diferencia de los objetos reactivos, no se realiza ningún desenvolvimiento cuando se accede a la ref como un elemento de un array reactivo o un tipo de colección nativo 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 Desenvolver en Templates
El desenvolvimiento de refs en templates solo se aplica si la ref es una propiedad de nivel superior en el contexto de renderizado del template.
En el ejemplo siguiente, 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 se espera:
template
{{ count + 1 }}...mientras que esta NO lo hace:
template
{{ object.id + 1 }}El resultado renderizado será [object Object]1 porque object.id no se desenvuelve al evaluar la expresión y permanece como un objeto ref. Para solucionar esto, podemos desestructurar id en una propiedad de nivel superior:
js
const { id } = objecttemplate
{{ id + 1 }}Ahora el resultado renderizado será 2.
Otra cosa a tener en cuenta es que una ref sí se desenvuelve 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 solo una característica de conveniencia de la interpolación de texto y es equivalente a {{ object.id.value }}.







