En este tutorial de Vue.js vamos a ver cómo funciona la reactividad a través de las funciones ref
y reactive
.
La reactividad es un paradigma de programación que nos permite que los cambios en los datos se reflejen automáticamente en la interfaz de usuario, creando una conexión entre los datos y el UI.
Por ejemplo, si tienes un contador en tu aplicación y aumentas su valor, Vue se encarga de que el número mostrado en la pantalla se actualice sin necesidad de manipular manualmente el DOM.
Es decir, cuando modificas una propiedad reactiva, Vue detectará automáticamente ese cambio y actualizará cualquier parte de la interfaz que dependa de esa propiedad.
La reactividad es uno de los pilares fundamentales de cualquier framework moderno. En Vue 3, se maneja principalmente a través de dos funciones
- Usa ref → Para valores simples
- Usa reactive → Para objetos y arays
Ambas nos permiten crear datos reactivos, pero se utilizan en diferentes contextos. Vamos a verlas en detalle
Reactividad de valores primitivos con ref
La función ref()
nos permite crear referencias reactivas a valores primitivos como strings, números o booleanos (aunque también puede contener objetos)
La sintaxis básica de ref
es la siguiente:
import { ref } from 'vue';
const count = ref(0); // Crear una referencia reactiva con valor inicial 0
Cuando creamos una variable con ref()
, Vue envuelve el valor en un objeto en una propiedad .value
. Así que para acceder o modificar este valor, debemos usar esta propiedad:
// Acceder al valor
console.log(contador.value) // 0
// Modificar el valor
contador.value++
console.log(contador.value) // 1
Es común olvidar utilizar .value cuando se está comenzando con Vue.
Sin embargo, en las plantillas de Vue no necesitamos usar .value
. Vue lo gestiona automáticamente por nosotros,
<template>
<p>El contador es: {{ count }}</p>
<button @click="count++">Incrementar</button>
</template>
Aquí accedemos al valor de count
sin necesidad de usar .value
porque Vue lo gestiona por nosotros
Reactividad para objetos y arrays con reactive
La función reactive()
nos permite crear objetos reactivos completos, donde todas sus propiedades son automáticamente reactivas. Está diseñado para manejar objetos y arrays.
La sintaxis básica de reactive
es la siguiente:
import { reactive } from 'vue';
const estado = reactive({
contador: 0,
mensaje: 'Hola, LuisLlamas.es!'
});
Con reactive
, podemos acceder y modificar las propiedades del objeto directamente, sin necesidad de usar .value
:
console.log(estado.contador); // Acceder al valor (0)
estado.contador++; // Incrementar el valor
console.log(estado.contador); // Ahora es 1
Cuando usar ref
y cuando reactive
La pregunta del millón, ¿Cuando usar ref
y cuando usar reactive
? En resumen
- ✔️ Se usar para valores primitivos (número, string, boolean, etc.)
- ✔️ Guarda el valor en una propiedad
.value
- ✔️ Hace que cualquier tipo (incluso objetos) sea reactivo, pero en un “envoltorio” (Ref)
const nombre = ref('Ana')
const contador = ref(0)
const activo = ref(true)
- ✔️ Se usa para objetos, arrays, mapas, sets, etc.
- ✔️ Convierte un objeto entero en reactivo (cada propiedad se vuelve reactiva).
- ✔️ Si anidas objetos dentro, también serán reactivos (reactividad profunda).
- ❌ No funciona para valores primitivos (te devolverá un valor “normal”)
const usuario = reactive({
nombre: 'Ana',
edad: 28,
direccion: {
calle: 'Principal',
ciudad: 'Barcelona'
},
preferencias: ['música', 'cine']
})
En realidad son más equivalentes de lo que parece. Si usamos ref
con objetos, Vue es suficientemente listo para utilizar reactive
dentro de value
.
Así que ref
conserva las mismas capacidades reacitvas que reactive
. Simplemente tenemos la molestia (pequeña) de utilizar value
para acceder al objeto
Reactividad profunda
Con reactividad profundad nos referimos a si las propiedades internas también van a ser reactivas (por ejemplo, obj.nombre, obj.detalles).
Tanto ref
como reactive
proporcionan reactividad profundad. Es decir, que estos dos casos van a funcionar correctamente.
<script setup>
import { ref } from 'vue'
const obj = ref({
nombre: 'Vue',
detalles: {
version: 3
}
})
function cambiarNombre() {
obj.value.nombre = 'Vue.js' // ✅ Reactivo
}
function cambiarVersion() {
obj.value.detalles.version = 4 // ✅ Reactivo
}
</script>
<template>
<div>
<p>{{ obj.nombre }}</p>
<button @click="cambiarNombre">Cambiar Nombre</button>
<p>{{ obj.detalles.version }}</p>
<button @click="cambiarVersion">Cambiar Versión</button>
</div>
</template>
Simplemente tenemos que acordarnos de usar value
<script setup>
import { reactive } from 'vue'
const obj = reactive({
nombre: 'Vue',
detalles: {
version: 3
}
})
function cambiarNombre() {
obj.nombre = 'Vue.js' // ✅ Reactivo
}
function cambiarVersion() {
obj.detalles.version = 4 // ✅ También reactivo
}
</script>
<template>
<div>
<p>{{ obj.nombre }}</p>
<button @click="cambiarNombre">Cambiar Nombre</button>
<p>{{ obj.detalles.version }}</p>
<button @click="cambiarVersion">Cambiar Versión</button>
</div>
</template>
Versiones Shallow
Sin embargo, la reactividad profunda tiene un coste importante de rendimiento, y no siempre vamos a necesitarla. Para ello tenemos shallowRef
y shallowReactive
, que son versiones superficiales. (es decir, sin reactividad profunda).
- Similar a
ref
, pero no hace profundo el valor si es un objeto. - Solo el valor asignado directamente es observado, no sus propiedades internas.
import { shallowRef } from 'vue'
const user = shallowRef({
name: 'Alice'
})
user.value.name = 'Bob' // No dispara reactividad automáticamente
user.value = { name: 'Charlie' } // Cambiar el objeto sí lo hace
- Igual que
reactive
, pero solo hace reactiva la primera capa del objeto. - Objetos anidados NO son reactivos.
import { shallowReactive } from 'vue'
const state = shallowReactive({
count: 0,
user: {
name: 'Alice'
}
})
// state.count es reactivo
// state.user.name NO es reactivo
Destructurar ref
o reactive
Destructurar tanto ref
como reactive
directamente va darnos problemas porque va a romper el sistema de reactividad (de hecho es un error habitual ⚠️).
const count = ref(10)
const { value } = count
value
aquí es una copia estática, ya no está vinculada acount
.- Ya no es reactivo, y cambiar
count.value
no actualizarávalue
, ni al revés.
const state = reactive({ count: 10, name: 'Alice' })
const { count, name } = state
count
yname
ahora son copias planas, ya no son reactivas.
En su lugar, podemos usar las siguientes funciones, que nos permiten destructurar o convertir entre los distintos tipos manteniendo de reactividad
Función | Descripción |
---|---|
toRef() | Convierte una propiedad de un objeto reactivo en un ref (útil para desestructurar). |
toRefs() | Convierte todas las propiedades de un objeto reactivo en refs. |
unref() | Obtiene el valor real de un ref automáticamente (ref.value , pero más elegante). |
La función toRefs()
convierte un objeto reactivo en un objeto plano donde cada propiedad es una referencia individual:
import { reactive, toRefs } from 'vue'
const estado = reactive({
nombre: 'Ana',
edad: 28
})
// Convertir a refs individuales
const { nombre, edad } = toRefs(estado)
// Ahora podemos usar nombre.value y mantener la reactividad
nombre.value = 'Carlos' // También actualiza estado.nombre
Similar a toRefs()
, pero para una sola propiedad:
import { reactive, toRef } from 'vue'
const estado = reactive({ contador: 0 })
const contadorRef = toRef(estado, 'contador')
contadorRef.value++ // También incrementa estado.contador
Podemos reconstruir objetos reactivos a partir de refs individuales:
import { ref, reactive } from 'vue'
const nombre = ref('Ana')
const edad = ref(28)
// Crear un objeto reactivo con refs
const usuario = reactive({
nombre,
edad
})
nombre.value = 'Carlos' // También actualiza usuario.nombre
usuario.edad = 29 // También actualiza edad.value
Versiones readonly
A veces vamos a querer crear una verisón versión inmutable (solo lectura) de un valor reactivo (ref
o reactive
), impidiendo su modificación directa. Por ejemplo, esto es útil si queremos proteger datos que no deberían ser modificados desde ciertos componentes o funciones.
Para ello tenemos las versionesreadonly
y shallowReadonly
. Estas
import { reactive, readonly } from 'vue'
const state = reactive({ count: 0 })
const readonlyState = readonly(state)
readonlyState.count++ // ❌ Error (en modo dev): Cannot assign to read only property
readonlyState
se puede leer pero no modificar- Si alguien intenta modificarlo, Vue dará un warning en desarrollo, pero no detendrá la ejecución en producción
- Aún es reactivo
readonly()
no congela el objeto como Object.freeze
— aún es reactivo internamente, solo previene escrituras externas.
Funcionamiento interno Avanzado
Por si en algun momento os interesa produnzar un poco en entender cómo funciona la reactividad y la “magia” que hace Vue.js, vamos a ver brevemente cómo funciona internamente.
Vue 3 implementa un sistema de reactividad basado en Proxies de JavaScript, que fue una gran mejora respecto al sistema basado en Object.defineProperty()
de Vue 2.
Básicamente,
- Al usar con variables reactivas, Vue emplea crea un Proxy alrededor del objeto original
- Durante la renderización o ejecución de efectos, Vue registra qué propiedades se acceden
- Cuando una propiedad cambia, Vue notifica a todos los efectos que dependen de esa propiedad
Es decir, si vieramos el proceso (muy simplificado) sería algo como lo siguiente.
// Esto es una simplificación conceptual, no código real de Vue
function createReactive(obj) {
return new Proxy(obj, {
get(target, key) {
// Registrar que alguien está accediendo a esta propiedad
track(target, key)
return target[key]
},
set(target, key, value) {
const oldValue = target[key]
target[key] = value
// Notificar a los efectos si el valor cambió
if (oldValue !== value) {
trigger(target, key)
}
return true
}
})
}
Vue.js realiza estas acciones, junto con un seguimiento de las dependencias de cada objeto tracking y un mecanismo de actualizacion de la vista trigger, para que todo sea cómodo de usar para nosotros.
En términos más técnicos, podemos definir la reactividad como un sistema que nos permite rastrear dependencias entre variables y ejecutar efectos secundarios cuando estas variables cambian.
Por otro lado, la necesidad de .value
se debe a las limitaciones de JavaScript con respecto a la reactividad de valores primitivos. A diferencia de los objetos, los valores primitivos se pasan por valor y no por referencia, lo que significa que Vue no podría rastrear rastrear sus cambios directamente.
Por ese motivo ref
es un wrapper para un valor primitivo, que permite envolver la variable en un objeto, permitiendo que el sistema de reactividad de Vue.js haga su trabajo.