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

SeniorVue.js #vue #reactivity

Вопрос

Как работает система реактивности во Vue 3 изнутри?

Короткий ответ

Vue 3 оборачивает объекты в Proxy, который перехватывает чтение и запись свойств. При чтении свойства во время рендера Vue запоминает "кто на него подписан", а при записи — уведомляет всех подписчиков перерендериться.

Подробный ответ

Два ключевых примитива:

  • reactive(obj) — оборачивает объект в Proxy. Перехватчики get/set реализуют отслеживание (track) и уведомление (trigger) зависимостей. Работает только с объектами/массивами.
  • ref(value) — обёртка с единственным свойством .value. Нужна для примитивов (number, string), которые нельзя проксировать напрямую. Внутри ref тоже использует Proxy-подобный геттер/сеттер через объект-обёртку.

Механизм зависимостей:

  1. Во время выполнения "эффекта" (например, функции рендера компонента или computed) Vue устанавливает текущий активный эффект.
  2. При чтении реактивного свойства сеттер/геттер Proxy регистрирует этот эффект как зависимость свойства (track).
  3. При записи в свойство Vue находит все зависимые эффекты и запускает их заново (trigger) — это и есть точечное обновление, без сравнения виртуального DOM целиком по всему дереву, как в React.

В шаблонах ref автоматически "разворачивается" (unwrap), поэтому пишут count, а не count.value — это делает компилятор шаблонов.

Пример

import { reactive, effect } from 'vue'

const state = reactive({ count: 0 })

effect(() => {
  console.log('Текущее значение:', state.count) // подписка на state.count
})

state.count++ // эффект выше выполнится заново автоматически

Дополнительные вопросы

  • Почему reactive не работает с примитивами, а ref — работает?
  • Что произойдёт при деструктуризации reactive-объекта и как это исправить (toRefs)?
  • Чем реактивность на Proxy (Vue 3) отличается от Object.defineProperty (Vue 2)?