Реактивные данные

Реактивность — самое фундаментальное понятие в Vue3.

В этой главе вы поймёте, что такое реактивность, и научитесь использоватьrefиreactiveдля управления данными, а также реализуете функциональность фильтрации по категориям.


Что такое реактивность?

Реактивность означает:Когда данные меняются, страница обновляется автоматически.。

Традиционный подход на JS выглядит так: после изменения переменной вам приходится вручную обновлять DOM.

Реактивная система Vue3 автоматически отслеживает изменения данных и уведомляет представление о необходимости обновления — вам нужно заботиться только о данных, а не о DOM.

Пример

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

// Переменные, обёрнутые в ref, реактивны
const count = ref(0)

// После изменения count, {{ count }} в шаблоне обновляется автоматически
function increment() {
count.value++ // Примечание: в JS для доступа нужен .value
}
</script>

<template>
  <p>Количество кликов: {{ count }}</p>
  <!-- Автоматически разворачивается в шаблоне, .value не нужен -->
  <button @click="increment">+1</button>
</template>

После нажатия кнопки число, отображаемое на странице, автоматически меняется — это реактивность.

Вам не нужно писать ничего,document.querySelectorилиinnerHTMLчтобы обновить страницу.


ref — Обработка примитивных типов

ref— наиболее часто используемый реактивный API в Vue3, подходящий для обёртки данных примитивных типов.

Примитивные типы включают: строки, числа, булевы значения, null, undefined.

Синтаксис

Пример

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

// Создание ref
const name = ref('EXAMPLE') // Строка
const age = ref(18) // Число
const isVip = ref(false) // Логическое значение

// Чтение .value
console.log(name.value) // "EXAMPLE"
console.log(age.value) // 18

// Изменение .value
name.value = 'Hello EXAMPLE'
age.value++
isVip.value = !isVip.value
</script>

<template>
  <p>Имя: {{ name }}</p>
  <!-- В шаблонах ref автоматически разворачивается, .value не нужен -->
  <p>Возраст: {{ age }}</p>
  <p>VIP:{{ isVip }}</p>
</template>

Самое важное, что нужно запомнить:В JS-коде нужно .value, но не в шаблонах.Это самая распространённая ошибка новичков.


reactive — Работа с объектами и массивами

reactiveПодходит для оборачивания данных ссылочного типа, таких как объекты или массивы.

В отличие от ref, reactive не нуждается в.value.value, и вы можете напрямую обращаться к свойствам.

Пример

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

// Оборачиваем объект с помощью reactive
const user = reactive({
name: 'Xiaoming',
  age: 20,
hobbies: ['programming', 'running']
})

// Прямой доступ и изменение, .value не нужен
console.log(user.name) // "Сяо Мин"
user.name = 'Xiao Hua' // Реактивное обновление
user.hobbies.push('reading') // Операции с массивами тоже реактивны
</script>

<template>
  <p>Имя: {{ user.name }}</p>
  <p>Возраст: {{ user.age }}</p>
  <ul>
    <li v-for="(hobby, index) in user.hobbies" :key="index">
      {{ hobby }}
    </li>
  </ul>
</template>

Два ограничения reactive

ОграничениеОписаниеРешение
Можно оборачивать только объектные типыНельзя передавать примитивные типы, такие как строки, числаИспользуйте ref для примитивных типов
Нельзя заменить целикомНельзя присвоить весь объект новому объектуИспользуйте Object.assign() или переключитесь на ref

Пример

import { reactive, ref } from 'vue'

// Ошибка: нельзя заменить реактивный объект целиком
const state = reactive({ count: 0 })
// state = { count: 1 } // Это не сработает, реактивность будет потеряна

// Правильный подход 1: изменяйте свойства вместо замены целиком
state.count = 1

// Правильный подход 2: используйте ref, чтобы обернуть объект (ref позволяет заменить его целиком)
const stateRef = ref({ count: 0 })
stateRef.value = { count: 1 }  // Это работает! Потому что .value у ref можно заменить

ref vs reactive: Когда что использовать

СценарийРекомендацияПример
Одиночная строка/число/логическое значениеrefconst name = ref('小明')
Одиночный массивrefconst list = ref([])
Одиночный объектРаботают и ref, и reactiveconst user = ref({})
Несколько несвязанных состоянийИспользовать ref для каждогоОдин ref на строку
Группа тесно связанных состоянийreactiveнапример, данные формы, коллекция конфигурации

Наиболее распространённая практика в реальной разработке:Всегда использовать ref, включая объекты и массивы。

Это обеспечивает единообразие стиля кода, и вам не нужно переключать свою мысленную модель между ref и reactive.

В следующих главах этого руководства повсеместно используется ref для управления всем состоянием. В местах, где требуется.valuereactive, эти места будут специально отмечены.


computed — Вычисляемые свойства

computed— это "данные, вычисленные из существующих данных."

У него есть две ключевые особенности:Кэширование——когда зависимости не меняются, он не пересчитывается;Автоматическое отслеживание——когда зависимости меняются, он обновляется автоматически.

Пример

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

const articles = ref([
Начало работы с Vue3
{ id: 2, title: 'Подробное объяснение Promise', category: 'JavaScript' },
Макет Grid
  { id: 4, title: 'Composition API', category: 'Vue' }
])

const activeCategory = ref('All')

// computed фильтрует articles на основе activeCategory
const filteredArticles = computed(() => {
if (activeCategory.value === 'All') {
    return articles.value
  }
  return articles.value.filter(a => a.category === activeCategory.value)
})

// computed также можно использовать для статистики
const totalCount = computed(() => articles.value.length)
const filteredCount = computed(() => filteredArticles.value.length)
</script>

<template>
  <p>Всего статей: {{ totalCount }}, сейчас показано: {{ filteredCount }}</p>

  <!-- Кнопки категорий -->
  <button @click=activeCategory = 'All'>Все</button>
  <button @click="activeCategory = 'Vue'">Vue</button>
  <button @click="activeCategory = 'JavaScript'">JavaScript</button>
  <button @click="activeCategory = 'CSS'">CSS</button>

  <!-- Список отфильтрованных статей -->
  <div v-for="article in filteredArticles" :key="article.id">
    <h3>{{ article.title }}</h3>
    <span>{{ article.category }}</span>
  </div>
</template>

Всякий раз, когдаactiveCategoryизменения,filteredArticlesон автоматически пересчитается, и страница автоматически обновится.

Примечание: computed возвращает ref, поэтому обращение к нему в JS также требует.value.value. Однако в приведённом выше примере мы использовали его напрямую в шаблоне, где он автоматически разворачивается.


Практика: добавление фильтрации по категориям в блог

Примените computed на главной странице блога для фильтрации статей при нажатии кнопок категорий.

Пример

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

const articles = ref([
{ id: 1, title: 'Vue3: полное руководство для начинающих', summary: 'изучение Vue3 с нуля', category: 'Vue', date: '2024-05-10' },
{ id: 2, title: 'JS: асинхронное программирование подробно', summary: Понимание Promise и async/await, category: 'JavaScript', date: '2024-05-08' },
{ id: 3, title: Практическая вёрстка с CSS Grid, summary: 'Реализация адаптивной вёрстки с помощью Grid', category: 'CSS', date: '2024-05-05' },
{ id: 4, title: 'Принципы реактивности Vue3', summary: 'Глубокое понимание ref и reactive', category: 'Vue', date: '2024-05-03' },
{ id: 5, title: 'Полное руководство по Flexbox', summary: 'Изучение flex-вёрстки в одной статье', category: 'CSS', date: '2024-05-01' },
])

// Все категории (без дубликатов)
const categories = computed(() => {
  const cats = articles.value.map(a => a.category)
return ['All', ...new Set(cats)] // Добавить 'All' в начало
})

// Текущая выбранная категория
const activeCategory = ref('All')

// Фильтровать статьи по категории
const filteredArticles = computed(() => {
if (activeCategory.value === 'All') {
    return articles.value
  }
  return articles.value.filter(a => a.category === activeCategory.value)
})
</script>

<template>
  <div class="home">
    <h2>Последние статьи</h2>

    <!-- Кнопки фильтра категорий -->
    <div class="category-bar">
      <button
       v-for="cat in categories"
       :key="cat"
       :class="{ active: activeCategory === cat }"
       @click="activeCategory = cat"
     >
        {{ cat }}
      </button>
    </div>

    <!-- Количество отфильтрованных результатов -->
    <p class="result-info">Всего {{ filteredArticles.length }} статей</p>

    <!-- Список статей -->
    <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>

Теперь, когда вы нажимаете разные кнопки категорий, список статей фильтруется в реальном времени. Вам не нужно писать код для манипуляции DOM; достаточно сосредоточиться на логике изменения данных.


Краткое содержание главы

В этой главе вы освоили три основных API системы реактивности Vue 3: ref оборачивает примитивные типы (в JS используется .value), reactive оборачивает объекты, а computed создаёт вычисляемые данные.

С помощью computed + кнопок фильтрации по категориям вы познакомились с шаблоном разработки «представления, управляемые данными» — измените данные, и страница обновится автоматически.

Другие расширения