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 ref
y reactive
.
- Para valores simples → Usa
ref
- Para objetos complejos → Usa
reactive
Ambas permiten crear datos reactivos, pero se utilizan en diferentes contextos. Vamos a verlas en detalle, y cuando usar cada una de ellas.
Reactividad de valores primitivos con ref
La función ref()
nos permite crear referencias reactivas a valores primitivos como strings, números o booleanos (también puede contener objetos, aunque veremos que reactive()
suele ser más adecuado para estructuras complejas)
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 con una única propiedad .value
.
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
Sin embargo, en las plantillas de Vue no necesitas usar .value
, ya que Vue lo maneja automáticamente por nosotros:
<template>
<p>El contador es: {{ count }}</p>
<button @click="count++">Incrementar</button>
</template>
Expande para ver un ejemplo completo 👇
<template>
<div>
<p>Contador: {{ count }}</p>
<button @click="incrementar">Incrementar</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
const count = ref(0);
function incrementar() {
count.value++;
}
</script>
En este ejemplo, cada vez que se hace clic en el botón, el valor de count
se incrementa y la interfaz se actualiza automáticamente.
Es común olvidar utilizar .value cuando se está comenzando con Vue 3.
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
Expande para ver un ejemplo completo 👇
<template>
<div>
<input v-model="formulario.nombre" placeholder="Nombre" />
<input v-model="formulario.edad" placeholder="Edad" type="number" />
<p>Nombre: {{ formulario.nombre }}</p>
<p>Edad: {{ formulario.edad }}</p>
</div>
</template>
<script setup>
import { reactive } from 'vue';
const formulario = reactive({
nombre: '',
edad: null
});
</script>
En este ejemplo, el objeto formulario
es reactivo. Cuando el usuario ingresa datos en los campos de entrada, los cambios se reflejan automáticamente en la interfaz.
Comparativa: ref() vs reactive()
Para ilustrar mejor las diferencias, comparemos directamente ambas APIs:
🆚 Característica | ref | reactive |
---|---|---|
Tipo de dato | Maneja valores primitivos y objetos | Solo objetos |
Acceso a valores | Requiere .value | Acceso directo a propiedades |
Desestructuración | ✅ Mantiene reactividad (con toRefs ) | ❌ Pierde reactividad |
Uso en valores simples | Ideal para valores primitivos (números, strings, booleanos) | No es ideal para valores simples |
Uso en objetos | Puede envolver objetos, pero hay que acceder mediante .value | Diseñado para objetos y arrays complejos |
Reactividad profunda | No es profunda por defecto para objetos (solo la referencia es reactiva) | ✅ Reactividad profunda por defecto (detecta cambios internos) |
Entonces, vamos a resumir cuando conviene usar uno u otro.
- ✔️ Trabajamos con valores primitivos (strings, números, booleanos)
- ✔️ Necesitamos pasar la reactividad a funciones o componentes
- ✔️ Queremos desestructurar valores manteniendo la reactividad
- ✔️ Tenemos un valor único que cambiará con el tiempo
const nombre = ref('Ana')
const contador = ref(0)
const activo = ref(true)
- ✔️ Trabajamos con estructuras de datos complejas
- ✔️ Tenemos múltiples propiedades relacionadas
- ✔️ No necesitamos desestructurar el objeto
- ✔️ Queremos una sintaxis más limpia sin
.value
const usuario = reactive({
nombre: 'Ana',
edad: 28,
direccion: {
calle: 'Principal',
ciudad: 'Barcelona'
},
preferencias: ['música', 'cine']
})
Reactividad profunda
Con reactividad profundad o nested reactive nos referimos a si las propiedades internas también van a ser reactivas (por ejemplo, obj.nombre, obj.detalles)
Característica | ref | reactive |
---|---|---|
Reactivo anidado | ❌ No | ✅ Sí |
Vamos a verlo con ejemplos,
<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 // ❌ NO es reactivo (no detecta el cambio)
}
</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>
Aquí, Vue solo detecta cambios en la referencia value
, pero no sigue las propiedades anidadas dentro de detalles
.
Si quieres que las propiedades internas también sean reactivas, puedes usar reactive
o shallowRef
.
<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 (¡nested reactive!)
}
</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>
Convertir entre ref() y reactive()
No es demasiado frecuente, pero en ocasiones puede ser necesario convertir entre los distintos tipos de reactividad. Para eso Vue.js nos proporciona estos mecanismos:
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
Esto es extremadamente útil cuando necesitamos desestructurar propiedades pero queremos mantener la conexión reactiva con el objeto original.
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
El proceso de seguimiento (tracking) 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.