vuejs-reactividad-ref-reactive

Qué es y cómo funciona la reactividad con Ref y Reactive en Vue.js

  • 9 min

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 a count.
  • 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 y name 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ónDescripció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,

  1. Al usar con variables reactivas, Vue emplea crea un Proxy alrededor del objeto original
  2. Durante la renderización o ejecución de efectos, Vue registra qué propiedades se acceden
  3. 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.