Datos reactivos

La reactividad es el concepto más central de Vue3.

En este capítulo entenderás qué es la reactividad y aprenderás a usarrefyreactivepara gestionar datos e implementar el filtrado por categorías.


¿Qué es la reactividad?

La reactividad significa:Cuando los datos cambian, la página se actualiza automáticamente。

La forma tradicional en JS es esta: modificas la variable y aún tienes que actualizar el DOM manualmente.

El sistema reactivo de Vue3 rastrea automáticamente los cambios de datos y notifica a la vista para que se actualice — solo necesitas preocuparte por los datos, no por el DOM.

Ejemplo

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

// La variable envuelta por ref es reactiva
const count = ref(0)

// Después de modificar count, {{ count }} en la plantilla se actualizará automáticamente
function increment() {
count.value++ // Nota: en JS es necesario acceder con .value
}
</script>

<template>
  <p>Número de clics: {{ count }}</p>
  <!-- En la plantilla se desempaqueta automáticamente, no se necesita .value -->
  <button @click="increment">+1</button>
</template>

Después de hacer clic en el botón, el número que se muestra en la página cambia automáticamente — eso es la reactividad.

No necesitas escribir ningúndocument.querySelectoroinnerHTMLpara actualizar la página.


ref — para tipos primitivos

refEs la API reactiva más utilizada en Vue3, adecuada para envolver datos de tipos primitivos.

Los tipos primitivos incluyen: cadenas, números, booleanos, null, undefined.

Sintaxis

Ejemplo

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

// Crear una ref
const name = ref('EJEMPLO') // cadena de texto
const age = ref(18) // número
const isVip = ref(false) // booleano

// Leer .value
console.log(name.value)         // "EJEMPLO"
console.log(age.value)          // 18

// Modificar .value
name.value = 'Hello EJEMPLO'
age.value++
isVip.value = !isVip.value
</script>

<template>
  <p>Nombre: {{ name }}</p>
  <!-- En la plantilla, la ref se desempaqueta automáticamente, no se necesita .value -->
  <p>Edad: {{ age }}</p>
  <p>VIP:{{ isVip }}</p>
</template>

El punto clave a recordar:En el código JS hay que añadir .value; en la plantilla no hace falta añadirlo. Es el error más común entre los principiantes.


reactive — para objetos y arrays

reactiveEs adecuado para envolver datos de tipos de referencia como objetos o arrays.

A diferencia de ref, reactive no necesita.value, basta con acceder directamente a las propiedades.

Ejemplo

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

// Envolver un objeto con reactive
const user = reactive({
name: 'Xiaoming',
  age: 20,
hobbies: ['programación', 'correr']
})

// Acceder y modificar directamente, no se necesita .value
console.log(user.name) // "Xiaoming"
user.name = 'Xiaohua' // actualización reactiva
user.hobbies.push('leer') // las operaciones con arrays también son reactivas
</script>

<template>
  <p>Nombre: {{ user.name }}</p>
  <p>Edad: {{ user.age }}</p>
  <ul>
    <li v-for="(hobby, index) in user.hobbies" :key="index">
      {{ hobby }}
    </li>
  </ul>
</template>

Dos limitaciones de reactive

LimitaciónDescripciónSolución
Solo puede envolver tipos de objetoNo se pueden pasar tipos primitivos como cadenas, números, etc.Para tipos primitivos usa ref
No se puede reemplazar por completoNo se puede asignar el objeto completo a un nuevo objetoUsa Object.assign() o cambia a ref

Ejemplo

import { reactive, ref } from 'vue'

// Error: no se puede reemplazar por completo un objeto reactive
const state = reactive({ count: 0 })
// state = { count: 1 } // Así no funciona, se pierde la reactividad

// Forma correcta 1: modifica propiedades en lugar de reemplazar todo
state.count = 1

// Forma correcta 2: envuelve el objeto con ref (ref permite reemplazar todo)
const stateRef = ref({ count: 0 })
stateRef.value = { count: 1 }  // ¡Se puede! Porque el .value de ref se puede reemplazar

ref vs reactive: cuándo usar cada uno

EscenarioRecomendaciónEjemplo
Una sola cadena/número/booleanorefconst name = ref('Xiaoming')
Un solo arrayrefconst list = ref([])
Un solo objetoref o reactive, cualquiera sirveconst user = ref({})
Varios estados no relacionadosUsa ref para cada unoUna ref por línea
Un conjunto de estados fuertemente relacionadosreactivePor ejemplo: datos de formularios, conjunto de opciones de configuración

La práctica más común en el desarrollo real es:Usar ref en todos los casos, incluyendo objetos y arrays。

Así la escritura es uniforme y no necesitas cambiar constantemente de modelo mental entre ref y reactive.

Las secciones siguientes de este tutorial usarán ref de manera uniforme para gestionar todos los estados; cuando se necesite.valuese indicará especialmente.


computed — propiedades computadas

computedSon "datos calculados a partir de datos existentes".

Tiene dos características principales:Almacenamiento en caché— cuando las dependencias no cambian, no se vuelve a calcular;Rastreo automático— cuando las dependencias cambian, se actualiza automáticamente.

Ejemplo

<script setup>
import { ref, computed } from 'vue'

const articles = ref([
{ id: 1, title: 'Introducción a Vue3', category: 'Vue' },
{ id: 2, title: 'Promise en detalle', category: 'JavaScript' },
{ id: 3, title: 'Diseño Grid', category: 'CSS' },
  { id: 4, title: 'Composition API', category: 'Vue' }
])

const activeCategory = ref('Todos')

// computed filtra los artículos según activeCategory
const filteredArticles = computed(() => {
if (activeCategory.value === 'Todos') {
    return articles.value
  }
  return articles.value.filter(a => a.category === activeCategory.value)
})

// computed también se puede usar para estadísticas
const totalCount = computed(() => articles.value.length)
const filteredCount = computed(() => filteredArticles.value.length)
</script>

<template>
  <p>Hay {{ totalCount }} artículos en total, actualmente se muestran {{ filteredCount }}</p>

  <!-- Botones de categoría -->
  <button @click="activeCategory = 'Todos'">Todos</button>
  <button @click="activeCategory = 'Vue'">Vue</button>
  <button @click="activeCategory = 'JavaScript'">JavaScript</button>
  <button @click="activeCategory = 'CSS'">CSS</button>

  <!-- Lista de artículos filtrados -->
  <div v-for="article in filteredArticles" :key="article.id">
    <h3>{{ article.title }}</h3>
    <span>{{ article.category }}</span>
  </div>
</template>

Cada vez queactiveCategorycambia,filteredArticlesse recalcula automáticamente y la página se actualiza sola.

Nota: computed devuelve una ref, por lo que en JS también se necesita.value. Pero en el ejemplo anterior lo usamos directamente en la plantilla, donde se desempaqueta automáticamente.


Manos a la obra: añade el filtrado por categorías al blog

Aplica computed a la página de inicio del blog para filtrar los artículos al hacer clic en los botones de categoría.

Ejemplo

<script setup>
import { ref, computed } from 'vue'

const articles = ref([
{ id: 1, title: 'Guía completa de introducción a Vue3', summary: 'Aprende Vue3 desde cero', category: 'Vue', date: '2024-05-10' },
{ id: 2, title: 'Programación asíncrona en JS en detalle', summary: 'Entiende Promise y async/await', category: 'JavaScript', date: '2024-05-08' },
{ id: 3, title: 'Práctica de diseño CSS Grid', summary: 'Implementa un diseño responsivo con Grid', category: 'CSS', date: '2024-05-05' },
{ id: 4, title: 'Principio de reactividad de Vue3', summary: 'Profundiza en ref y reactive', category: 'Vue', date: '2024-05-03' },
{ id: 5, title: 'Guía completa de Flexbox', summary: 'Aprende el diseño flexible de una vez', category: 'CSS', date: '2024-05-01' },
])

// Todas las categorías (sin duplicados)
const categories = computed(() => {
  const cats = articles.value.map(a => a.category)
return ['Todas', ...new Set(cats)] // Añade "Todas" al principio
})

// Categoría actualmente seleccionada
const activeCategory = ref('Todas')

// Filtrar artículos por categoría
const filteredArticles = computed(() => {
if (activeCategory.value === 'Todas') {
    return articles.value
  }
  return articles.value.filter(a => a.category === activeCategory.value)
})
</script>

<template>
  <div class="home">
    <h2>Artículos recientes</h2>

    <!-- Botones de filtro de categoría -->
    <div class="category-bar">
      <button
       v-for="cat in categories"
       :key="cat"
       :class="{ active: activeCategory === cat }"
       @click="activeCategory = cat"
     >
        {{ cat }}
      </button>
    </div>

    <!-- Número de resultados filtrados -->
    <p class="result-info">Total: {{ filteredArticles.length }} artículos</p>

    <!-- Lista de artículos -->
    <div class="article-grid">
      <div v-for="article in filteredArticles" :key="article.id" class="card">
        <span class="tag">{{ article.category }}</span>
        <h3>{{ article.title }}</h3>
        <p>{{ article.summary }}</p>
        <span class="date">{{ article.date }}</span>
      </div>
    </div>
  </div>
</template>

<style scoped>
.category-bar {
  display: flex;
  gap: 10px;
  margin-bottom: 20px;
  flex-wrap: wrap;
}

.category-bar button {
  padding: 6px 16px;
  border: 1px solid #ddd;
  border-radius: 20px;
  background: #fff;
  cursor: pointer;
  font-size: 14px;
  transition: all 0.2s;
}

.category-bar button.active {
  background: #427DB8;
  color: #fff;
  border-color: #427DB8;
}

.category-bar button:hover {
  border-color: #427DB8;
}

.result-info {
  color: #999;
  font-size: 14px;
  margin-bottom: 16px;
}
</style>

Ahora, al hacer clic en los diferentes botones de categoría, la lista de artículos se filtra en tiempo real. No necesitas escribir ningún código de manipulación del DOM, solo debes prestar atención a la lógica de los cambios de datos.


Resumen del capítulo

En este capítulo has dominado las tres API principales del sistema de reactividad de Vue3: ref envuelve tipos primitivos (en JS se usa .value), reactive envuelve objetos y computed deriva datos calculados.

Mediante computed + el botón de filtro de categorías, has experimentado el patrón de desarrollo de "vista impulsada por datos": al cambiar los datos, la página se actualiza automáticamente.

Extensiones adicionales