tutoriales.com

Domina la Reactividad: Explorando Refs y Reactive en Vue 3 para una Gestión de Estado Eficiente

Este tutorial profundiza en los mecanismos de reactividad de Vue 3, explicando detalladamente cómo y cuándo utilizar `ref` y `reactive`. Aprenderás a gestionar el estado de tus componentes de manera eficiente y a construir aplicaciones Vue más robustas.

Intermedio18 min de lectura10 views19 de marzo de 2026Reportar error

🚀 Introducción a la Reactividad en Vue 3

Vue.js es famoso por su sistema de reactividad, que permite que tu interfaz de usuario se actualice automáticamente cuando los datos cambian. En Vue 3, este sistema ha sido mejorado y simplificado con la introducción de la Composition API y dos funciones clave para la declaración de estado reactivo: ref y reactive.

Comprender cuándo usar cada una y cómo interactúan entre sí es fundamental para escribir código Vue 3 limpio, eficiente y fácil de mantener. Este tutorial te guiará a través de los conceptos, diferencias y mejores prácticas para dominar ref y reactive.

🔥 Importante: La reactividad es el corazón de Vue. Sin ella, tendríamos que manipular el DOM manualmente, lo cual es propenso a errores y engorroso.

📚 Fundamentos de la Reactividad en Vue 3

Antes de sumergirnos en ref y reactive, recordemos qué significa "reactividad" en el contexto de Vue. Cuando declaramos datos como reactivos, Vue crea "observadores" alrededor de esos datos. Si los datos observados cambian, Vue detecta esa mutación y automáticamente actualiza cualquier parte de la interfaz de usuario que dependa de ellos.

En Vue 3, esto se logra principalmente a través de proxies. Cuando accedes o modificas una propiedad de un objeto reactivo, el proxy intercepta la operación y notifica a Vue sobre el cambio, permitiendo que el sistema de reactividad haga su magia.

La Composition API y su Papel

La Composition API, introducida en Vue 3, es un conjunto de APIs que nos permite componer la lógica de nuestros componentes de manera más flexible y organizada. ref y reactive son pilares de esta API, permitiendo declarar el estado reactivo directamente dentro de la función setup() de un componente o en funciones composables.

// Ejemplo básico de setup()
import { defineComponent, ref } from 'vue';

export default defineComponent({
  setup() {
    const count = ref(0);

    function increment() {
      count.value++;
    }

    return {
      count,
      increment
    };
  }
});

🔄 Entendiendo ref

La función ref() es la forma recomendada de declarar variables reactivas que contienen valores primitivos (strings, numbers, booleans, null, undefined) o referencias a objetos. Cuando usas ref(), Vue envuelve el valor en un objeto especial que tiene una única propiedad value.

¿Por qué value?

Los valores primitivos en JavaScript se pasan por valor, no por referencia. Si ref() simplemente devolviera el valor primitivo directamente, Vue no podría detectar cuándo ha cambiado. Al envolverlo en un objeto con una propiedad .value, Vue puede interceptar las lecturas y escrituras a esa propiedad, haciendo que el valor sea reactivo.

import { ref } from 'vue';

const myNumber = ref(10);
const myString = ref('Hola Vue');
const myBoolean = ref(true);

console.log(myNumber.value); // 10
myNumber.value++;
console.log(myNumber.value); // 11

// También se puede usar con objetos, pero hay un matiz
const myObjectRef = ref({ name: 'Alice' });
console.log(myObjectRef.value.name); // Alice
myObjectRef.value.name = 'Bob'; // Esto es reactivo
💡 Consejo: Dentro del `
INICIO ¿Es un valor primitivo? (string, number, boolean) Usar ref NO ¿Es un objeto complejo con múltiples propiedades? Usar reactive NO Usar ref (con .value) (Objeto simple o reasignación completa)

Desempaquetado (Unwrapping) de ref

Vue 3 realiza un desempaquetado automático de ref en el <template>. Esto significa que no necesitas escribir .value cuando te refieres a una ref dentro de tus plantillas. Esto mejora la legibilidad.

<template>
  <p>Contador: {{ count }}</p> <!-- Aquí 'count' se desempaqueta automáticamente -->
  <button @click="increment">Incrementar</button>
</template>

<script setup>
import { ref } from 'vue';

const count = ref(0);
const increment = () => {
  count.value++; // Aquí sí necesitas .value
};
</script>

También hay desempaquetado cuando una ref es una propiedad de un objeto reactive:

import { ref, reactive } from 'vue';

const count = ref(1);
const state = reactive({
  count
});

console.log(state.count); // 1 (sin .value)
state.count++;            // Modifica la ref original: count.value es ahora 2
console.log(count.value); // 2

🎯 Mejores Prácticas y Consejos Avanzados

Consistencia es Clave

Aunque ref y reactive tienen sus casos de uso específicos, en muchos proyectos se opta por una mayor consistencia. Una práctica común es usar ref para todo el estado reactivo, incluso para objetos.

Argumentos a favor de usar ref para todo:

  • Uniformidad: Siempre sabes que debes usar .value para acceder al valor (excepto en el template), lo que reduce la ambigüedad.
  • Reasignación: Permite reasignar el valor completo, lo cual es útil cuando se reemplazan objetos (por ejemplo, después de una carga de API).
  • Desempaquetado automático: Los objetos dentro de un ref se hacen reactivos por sí mismos, y su acceso es consistente.
// Usando ref para un objeto
import { ref } from 'vue';

const user = ref({
  firstName: 'Jane',
  lastName: 'Doe'
});

user.value.firstName = 'Janet'; // Esto es reactivo
user.value = { firstName: 'John', lastName: 'Smith' }; // Reemplazo reactivo

Argumentos a favor de usar reactive para objetos:

  • Sintaxis más limpia: Para objetos complejos, evitar .value en cada acceso a propiedad puede hacer el código más conciso.
  • Claridad de intención: Deja claro que estás tratando con un objeto que será mutado, no reemplazado.
💡 Consejo: Elige una convención para tu equipo y cúmplela. Si no estás seguro, `ref` es a menudo una apuesta más segura por su flexibilidad.

toRefs y toRef para desestructurar propiedades reactivas

Cuando desestructuramos un objeto reactive, perdemos la reactividad de sus propiedades. Para evitar esto, Vue proporciona las utilidades toRefs y toRef.

  • toRefs(reactiveObject): Convierte cada propiedad del objeto reactive en un ref individual. Esto es útil cuando devuelves un objeto reactive de una función composable y quieres que las propiedades se puedan desestructurar y seguir siendo reactivas.
import { reactive, toRefs } from 'vue';

function useUser() {
  const user = reactive({
    name: 'Alice',
    age: 30
  });

  function birthday() {
    user.age++;
  }

  // Si devuelves user directamente, al desestructurar en el componente
  // { name, age } = useUser(), se perdería la reactividad de name y age
  // Al usar toRefs, name y age serán refs y mantendrán la reactividad
  return { ...toRefs(user), birthday };
}

// En un componente:
<script setup>
import { useUser } from './useUser';
const { name, age, birthday } = useUser();
</script>

<template>
  <p>{{ name }} tiene {{ age }} años</p>
  <button @click="birthday">Cumplir años</button>
</template>
  • toRef(reactiveObject, 'property'): Crea una ref para una propiedad específica de un objeto reactive. Es útil cuando solo necesitas desestructurar o pasar una única propiedad manteniendo su reactividad.
import { reactive, toRef } from 'vue';

const user = reactive({ name: 'Bob', age: 25 });

const userAge = toRef(user, 'age');

console.log(userAge.value); // 25
userAge.value++;
console.log(user.age);      // 26 (la propiedad original también se actualiza)

El Impacto en las Funciones Composable

Las funciones composables son una de las características más potentes de la Composition API. Permiten reutilizar lógica con estado reactivo entre componentes.

Cuando creas un composable que expone estado reactivo, a menudo querrás devolver refs o usar toRefs para que el estado pueda ser desestructurado de forma segura por el componente que lo usa.

// useCounter.js
import { ref, computed } from 'vue';

export function useCounter(initialValue = 0) {
  const count = ref(initialValue);

  const doubleCount = computed(() => count.value * 2);

  const increment = () => {
    count.value++;
  };

  const decrement = () => {
    count.value--;
  };

  return { count, doubleCount, increment, decrement };
}

// MyComponent.vue
<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Double Count: {{ doubleCount }}</p>
    <button @click="increment">Increment</button>
    <button @click="decrement">Decrement</button>
  </div>
</template>

<script setup>
import { useCounter } from './useCounter';

const { count, doubleCount, increment, decrement } = useCounter(10);
</script>
Reactividad dominada al 90%

🛠️ Ejemplos Prácticos

Vamos a consolidar lo aprendido con un par de ejemplos prácticos que ilustran el uso de ref y reactive en escenarios comunes.

Ejemplo 1: Un Contador Simple con ref

Un clásico para empezar, usando ref para un valor primitivo.

<template>
  <div class="counter-app">
    <h2>Contador Vue 3</h2>
    <p>Valor actual: <mark>{{ count }}</mark></p>
    <button @click="increment">Incrementar</button>
    <button @click="decrement">Decrementar</button>
    <button @click="reset">Resetear</button>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const count = ref(0); // Declaramos una ref para el contador

const increment = () => {
  count.value++; // Accedemos y modificamos el valor usando .value
};

const decrement = () => {
  count.value--;
};

const reset = () => {
  count.value = 0;
};
</script>

<style scoped>
.counter-app {
  font-family: Arial, sans-serif;
  text-align: center;
  margin-top: 50px;
  padding: 20px;
  border: 1px solid #eee;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.counter-app button {
  margin: 0 5px;
  padding: 8px 15px;
  cursor: pointer;
  border: none;
  border-radius: 4px;
  background-color: #42b983;
  color: white;
  font-size: 16px;
}
.counter-app button:hover {
  background-color: #36a374;
}
</style>

Ejemplo 2: Un Formulario de Perfil de Usuario con reactive

Ideal para objetos con múltiples propiedades.

<template>
  <div class="profile-form">
    <h2>Editar Perfil de Usuario</h2>
    <form @submit.prevent="saveProfile">
      <div class="form-group">
        <label for="name">Nombre:</label>
        <input type="text" id="name" v-model="user.name" />
      </div>
      <div class="form-group">
        <label for="email">Email:</label>
        <input type="email" id="email" v-model="user.email" />
      </div>
      <div class="form-group">
        <label for="bio">Biografía:</label>
        <textarea id="bio" v-model="user.bio"></textarea>
      </div>
      <button type="submit">Guardar Perfil</button>
    </form>
    <div class="current-profile">
      <h3>Perfil Actual:</h3>
      <p><strong>Nombre:</strong> {{ user.name }}</p>
      <p><strong>Email:</strong> {{ user.email }}</p>
      <p><strong>Biografía:</strong> {{ user.bio }}</p>
    </div>
  </div>
</template>

<script setup>
import { reactive } from 'vue';

// Declaramos un objeto reactivo para el estado del usuario
const user = reactive({
  name: 'Ana García',
  email: 'ana.garcia@example.com',
  bio: 'Desarrolladora web apasionada por Vue.js'
});

const saveProfile = () => {
  // Aquí enviarías los datos a una API o los guardarías localmente
  alert('Perfil guardado: ' + JSON.stringify(user, null, 2));
  console.log('Datos del usuario:', user);
};
</script>

<style scoped>
.profile-form {
  font-family: Arial, sans-serif;
  margin: 30px auto;
  padding: 25px;
  border: 1px solid #ddd;
  border-radius: 10px;
  max-width: 500px;
  box-shadow: 0 4px 8px rgba(0,0,0,0.05);
  background-color: #f9f9f9;
}
.form-group {
  margin-bottom: 15px;
}
.form-group label {
  display: block;
  margin-bottom: 5px;
  font-weight: bold;
  color: #333;
}
.form-group input[type="text"],
.form-group input[type="email"],
.form-group textarea {
  width: calc(100% - 20px);
  padding: 10px;
  border: 1px solid #ccc;
  border-radius: 5px;
  font-size: 16px;
}
.form-group textarea {
  min-height: 80px;
  resize: vertical;
}
button[type="submit"] {
  display: block;
  width: 100%;
  padding: 12px;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 5px;
  font-size: 18px;
  cursor: pointer;
  transition: background-color 0.3s ease;
}
button[type="submit"]:hover {
  background-color: #0056b3;
}
.current-profile {
  margin-top: 25px;
  padding-top: 20px;
  border-top: 1px dashed #e0e0e0;
}
.current-profile h3 {
  color: #555;
  margin-bottom: 10px;
}
.current-profile p {
  margin-bottom: 5px;
  line-height: 1.5;
}
</style>

🔚 Conclusión

Dominar ref y reactive es un paso crucial para escribir aplicaciones Vue 3 eficientes y mantenibles. Mientras que ref es versátil y se adapta bien a cualquier tipo de valor (especialmente primitivos y objetos que pueden ser reemplazados), reactive brilla con objetos complejos donde la mutación de propiedades es la norma.

Recuerda la regla general: ref para valores primitivos o cuando necesitas reasignar la variable por completo; reactive para objetos complejos cuando las mutaciones de propiedades internas son el objetivo principal.

Independientemente de tu elección, la clave es la consistencia dentro de tu proyecto y la comprensión profunda de cómo funciona el sistema de reactividad de Vue 3. ¡Ahora estás listo para construir componentes más dinámicos y robustos!

FAQs Frecuentes sobre Reactividad

P: ¿Qué pasa si paso una ref o un objeto reactive como prop a un componente hijo?

R: Las refs y los objetos reactive son reactivos de extremo a extremo. Si pasas cualquiera de ellos como prop, el componente hijo también recibirá una versión reactiva. Si el padre actualiza el valor, el hijo se actualizará (a menos que el hijo intente mutar la prop directamente, lo cual es una mala práctica).

P: ¿Puedo usar reactive con un array?

R: Sí, puedes. reactive([]) funcionará, y las mutaciones de array (como push, pop, splice) serán detectadas. Sin embargo, si necesitas reemplazar el array completo por uno nuevo (myArray = [1, 2, 3]), la reactividad se perderá. En esos casos, ref([]) es más adecuado, donde myArray.value = [1, 2, 3] mantendría la reactividad.

P: ¿Hay algún coste de rendimiento al usar demasiados refs o reactives?

R: El sistema de reactividad de Vue 3 es altamente optimizado. Para la mayoría de las aplicaciones, el impacto en el rendimiento es insignificante. Elige el que mejor se adapte a tu lógica y mantenibilidad del código. Solo en aplicaciones con muchísimos objetos reactivos y actualizaciones muy frecuentes podría ser necesario optimizar, pero son casos extremos.

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!