Реактивные переменные: ref, reactive, computed

Vue.js строится на реактивной системе, которая позволяет автоматически отслеживать изменения данных и обновлять представление без ручного вмешательства. Основными строительными блоками реактивности являются ref, reactive и computed. Понимание их особенностей и различий критично для создания эффективных и производительных приложений.


ref — создание простых реактивных значений

ref используется для создания реактивных примитивов и объектов, оборачивая значение в реактивную оболочку. При этом Vue отслеживает доступ к значению и его изменения.

Синтаксис:

import { ref } from 'vue';

const count = ref(0);
  • Доступ к значению: через свойство .value
console.log(count.value); // 0
count.value++;
  • Реактивность примитивов: любые изменения через .value автоматически обновляют связанные компоненты или вычисления.
  • Реактивность объектов и массивов: можно использовать с объектами, но для глубоких структур лучше reactive.

reactive — глубокая реактивность объектов

reactive используется для создания реактивных объектов и массивов, позволяя Vue отслеживать изменения на всех уровнях вложенности.

Синтаксис:

import { reactive } from 'vue';

const state = reactive({
  user: { name: 'Alice', age: 25 },
  todos: []
});
  • Глубокая реактивность: изменения в state.user.name или добавление элементов в state.todos автоматически отслеживаются.
  • Не нужно использовать .value: доступ к свойствам объекта осуществляется напрямую.
  • Ограничения: нельзя передавать реактивные объекты между компонентами без использования toRefs, иначе теряется реактивность отдельных свойств при деструктуризации.

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

computed позволяет создавать производные значения на основе реактивных данных. Vue автоматически кеширует результат и обновляет его только при изменении зависимостей.

Синтаксис:

import { computed, ref } from 'vue';

const firstName = ref('Alice');
const lastName = ref('Smith');

const fullName = computed(() => `${firstName.value} ${lastName.value}`);
  • Ленивая вычисляемость: функция вычисляется только при обращении к fullName.value.
  • Кеширование: если зависимости не изменились, повторные обращения возвращают закешированное значение.
  • Двусторонняя связь: можно использовать setter для двустороннего связывания:
const fullName = computed({
  get: () => `${firstName.value} ${lastName.value}`,
  set: (val) => {
    const [first, last] = val.split(' ');
    firstName.value = first;
    lastName.value = last;
  }
});

Сравнение ref и reactive

Характеристика ref reactive
Тип данных Примитивы и объекты Объекты и массивы
Доступ к значению .value Прямой доступ
Глубокая реактивность Нет (только при ref({})) Да
Подходит для деструктуризации Да Только через toRefs

ref удобен для отдельных значений, reactive — для сложных структур состояния.


Реактивность в компонентах

Пример использования реактивных данных в компоненте:

<template>
  <div>
    <p>Счётчик: {{ count }}</p>
    <button @click="increment">Увеличить</button>
    <p>Полное имя: {{ fullName }}</p>
  </div>
</template>

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

const count = ref(0);
const firstName = ref('Alice');
const lastName = ref('Smith');

const fullName = computed(() => `${firstName.value} ${lastName.value}`);

function increment() {
  count.value++;
}
</script>
  • Любые изменения count.value, firstName.value или lastName.value автоматически отражаются в шаблоне.
  • Использование computed для fullName обеспечивает автоматическое пересчитывание при изменении зависимостей.

Комбинирование reactive и computed

Для сложного состояния удобно объединять объекты reactive с вычисляемыми свойствами:

import { reactive, computed } from 'vue';

const state = reactive({
  items: [1, 2, 3],
  discount: 0.1
});

const total = computed(() => {
  return state.items.reduce((sum, item) => sum + item, 0) * (1 - state.discount);
});
  • Любые изменения в state.items или state.discount автоматически обновляют total.
  • computed позволяет избежать повторных вычислений при неизменных данных.

Практические советы

  1. Выбор между ref и reactive:

    • Для примитивов и отдельных значений — ref.
    • Для сложных объектов и массивов — reactive.
  2. Деструктуризация reactive: использовать toRefs или toRef, чтобы сохранить реактивность.

  3. Избегать ненужных computed: использовать их только для вычислений, которые зависят от реактивных данных и требуют кеширования.

  4. Слежение за производительностью: глубокая реактивность reactive может иметь накладные расходы при больших структурах данных; для массивов с частыми изменениями стоит рассмотреть ref([]).

  5. Комбинирование с watch: computed хорошо для производных данных, а watch — для побочных эффектов при изменении реактивных значений.


Эти механизмы формируют основу реактивного программирования в Vue.js, позволяя строить динамичные интерфейсы с минимальной необходимостью ручного управления состоянием.