Как работает реактивность во Vue?

Во Vue реактивность — это система, которая позволяет автоматически отслеживать зависимости и обновлять DOM при изменении состояния данных. Эта система является ключевым элементом Vue и реализована на основе перехвата операций чтения и записи свойств объектов.

1. Основы реактивности

Vue создает реактивные объекты, оборачивая обычные JavaScript-объекты с помощью прокси (Proxy в Vue 3, Object.defineProperty в Vue 2). Это позволяет Vue отслеживать, когда данные читаются или изменяются, и запускать соответствующие обновления.

Пример:

const state = reactive({ count: 0 });
state.count++; // Триггер обновления

В этом примере state — реактивный объект. Когда происходит изменение state.count, Vue узнаёт об этом и обновляет компоненты, которые зависят от этого значения.

2. Реализация реактивности (Vue 3)

a) Proxy

В Vue 3 используется Proxy для создания реактивности. Это позволяет отслеживать все операции доступа (get, set, deleteProperty, и т. д.) над объектом.

const handler = {
get(target, key, receiver) {
track(target, key); // отслеживание зависимости
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
trigger(target, key); // уведомление об изменении
return result;
}
};
const proxy = new Proxy(target, handler);

b) track() и trigger()

  • track(target, key) — вызывается при чтении значения, регистрирует зависимость.

  • trigger(target, key) — вызывается при записи значения, оповещает подписчиков и обновляет DOM.

Vue ведет учет зависимостей с помощью структуры Map → Map → Set:

targetMap = Map {
target1 => Map {
key1 => Set(\[effect1, effect2\]),
key2 => Set(\[effect3\])
}
}

3. effect() — реактивный контекст

effect() — это функция, которая выполняет реактивный код и автоматически подписывается на все реактивные значения, к которым обращается.

effect(() => {
console.log(state.count); // подписка на state.count
});

При первом выполнении effect() вызывается track() на state.count. Когда state.count изменится, trigger() вызовет повторное выполнение effect.

4. Композиция реактивности

a) reactive()

Создает глубокий реактивный объект. Все вложенные свойства тоже становятся реактивными.

const state = reactive({ nested: { count: 0 } });

b) ref()

Создает реактивное значение-обертку. Используется для примитивов.

const count = ref(0);
count.value++; // Обязательно использовать .value

c) computed()

Создает вычисляемое реактивное значение, зависящее от других реактивных значений.

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

computed кэширует значение и пересчитывает его только при изменении зависимостей.

d) watch()

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

watch(() => state.count, (newVal, oldVal) => {
console.log(newVal, oldVal);
});

5. Особенности

a) Рекурсивная реактивность

reactive() делает вложенные объекты реактивными при первом доступе. Это ленивый процесс: если вложенное свойство не используется, оно не оборачивается в Proxy.

b) Ограничения реактивности

  • Нельзя сделать реактивным Symbol, WeakMap, WeakSet.

  • Потеря реактивности возможна при деструктуризации:

const { count } = state; // count не реактивен

Чтобы сохранить реактивность, используйте toRefs(state):

const { count } = toRefs(state); // теперь count  это ref

6. Vue 2: Реактивность через Object.defineProperty

В Vue 2 реактивность реализована через Object.defineProperty, который перехватывает get и set, но только на уровне свойств объекта, не поддерживая динамическое добавление или удаление свойств.

Object.defineProperty(obj, 'key', {
get() {
// track
},
set(val) {
// trigger
}
});

Из-за этого в Vue 2 требовались специальные методы:

  • Vue.set(obj, key, value)

  • Vue.delete(obj, key)

Vue 3 избавился от этих ограничений благодаря Proxy.

7. Внутреннее обновление компонента

Когда реактивное значение изменяется:

  1. Вызывается trigger(), уведомляющий все effect, зависящие от этого значения.

  2. Если один из effect связан с обновлением компонента, Vue планирует повторный рендер этого компонента через систему задач (scheduler).

  3. Компонент перерисовывается только при необходимости (система батчинга и оптимизации в Virtual DOM).

8. Важные API и утилиты

  • isReactive() — проверка, является ли объект реактивным.

  • isRef() — проверка, является ли значение ref.

  • unref() — извлечение значения из ref или возврат обычного значения.

  • toRaw() — извлечение оригинального объекта без прокси.

  • markRaw() — исключение объекта из реактивной системы.

  • shallowReactive() / shallowRef() — реактивность только верхнего уровня.

9. Рендеринг и реактивность

Vue автоматически отслеживает все реактивные зависимости внутри setup() и шаблона компонента. Когда одна из зависимостей изменяется, компонент автоматически перерисовывается.

<template>
<div>{{ count }}</div>
</template>
<script setup>
import { ref } from 'vue';
const count = ref(0);
</script>

Изменение count.value вызывает trigger(), что приводит к повторному рендеру шаблона.

Таким образом, реактивность Vue — это прозрачная, производительная и гибкая система, построенная на Proxy и механизмах отслеживания зависимостей.