Observer паттерн в реактивности

Реактивность во Vue.js строится вокруг паттерна Observer (Наблюдатель). Его задача — автоматически отслеживать зависимости между состоянием и представлением и запускать обновления при изменении данных. Реализация этого паттерна во Vue эволюционировала: от Object.defineProperty во Vue 2 к Proxy во Vue 3, при этом концептуальная модель осталась прежней — есть источник изменений, есть подписчики, есть механизм оповещения.


Классическая схема Observer

В абстрактном виде паттерн состоит из трёх элементов:

  • Subject (наблюдаемый объект) — хранит состояние.
  • Observer (наблюдатель) — реагирует на изменения состояния.
  • Механизм подписки — связывает наблюдателей с наблюдаемым объектом.

Во Vue роль Subject выполняют реактивные данные, а роль Observer — вычисляемые значения, компоненты и эффекты рендеринга.


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

Во Vue 2 каждая реактивная переменная превращается в геттер и сеттер:

Object.defineProperty(obj, 'count', {
  get() {
    return value
  },
  set(newValue) {
    value = newValue
    notify()
  }
})

Как это работает

  • get — регистрирует зависимость.
  • set — уведомляет всех подписчиков.
  • notify — запускает обновление компонентов и вычисляемых свойств.

Эта логика инкапсулирована в классе Observer, который рекурсивно обходит объект и делает каждое поле реактивным.


Dep и Watcher — внутренние наблюдатели

Внутри Vue 2 паттерн Observer реализован через связку Dep и Watcher.

Dep (Dependency)

  • Хранит список подписчиков.
  • Предоставляет методы depend() и notify().
class Dep {
  subs = []
  depend() {
    if (Dep.target) {
      this.subs.push(Dep.target)
    }
  }
  notify() {
    this.subs.forEach(w => w.update())
  }
}

Watcher

  • Представляет наблюдателя.
  • Связывается с конкретным участком состояния.
  • Реагирует на изменения и запускает перерендер или пересчёт.
class Watcher {
  UPDATE() {
    // повторный рендер или вычисление
  }
}

Во время рендера компонента текущий Watcher помещается в Dep.target, что позволяет автоматически собрать зависимости.


Автоматический сбор зависимостей

Ключевая особенность реализации — отсутствие явной подписки. Зависимости собираются во время выполнения:

  1. Компонент начинает рендер.
  2. Активируется соответствующий Watcher.
  3. Доступ к реактивному свойству вызывает get.
  4. get регистрирует Watcher в Dep.
  5. При изменении значения вызывается notify.

Этот механизм обеспечивает минимальное количество обновлений и точечный ререндер.


Ограничения подхода Vue 2

Использование Object.defineProperty накладывает ряд ограничений:

  • Невозможность отследить добавление новых свойств.
  • Проблемы с массивами (перехватываются только методы).
  • Необходимость обхода всего объекта при инициализации.

Из-за этого появились вспомогательные методы вроде Vue.se t.


Vue 3 и Proxy: новый уровень Observer

Во Vue 3 реактивность переписана с использованием Proxy.

const state = new Proxy(target, {
  get(target, key) {
    track(target, key)
    return target[key]
  },
  set(target, key, value) {
    target[key] = value
    trigger(target, key)
    return true
  }
})

Преимущества Proxy

  • Перехват любых операций: чтение, запись, удаление.
  • Реактивность для новых свойств без дополнительных методов.
  • Отсутствие необходимости рекурсивно обходить объект.
  • Более чистая и универсальная реализация Observer.

track и trigger — современная реализация подписки

Во Vue 3 роль Dep выполняет система хранилищ зависимостей:

  • track — регистрирует зависимость между эффектом и свойством.
  • trigger — уведомляет все эффекты, зависящие от свойства.
const targetMap = new WeakMap()

Использование WeakMap позволяет автоматически очищать неиспользуемые зависимости и предотвращать утечки памяти.


Эффекты как наблюдатели

Watcher во Vue 3 заменён на effect:

effect(() => {
  console.log(state.count)
})
  • effect регистрируется как наблюдатель.
  • Любые реактивные свойства внутри автоматически отслеживаются.
  • При изменении значения эффект перезапускается.

Компоненты, вычисляемые свойства и watch-функции построены поверх этого механизма.


Глубина реактивности

По умолчанию Vue делает реактивность глубокой:

  • Вложенные объекты также становятся наблюдаемыми.
  • Для оптимизации доступна shallowReactive — поверхностная реактивность.
  • Это позволяет точнее контролировать Observer-поведение в сложных структурах данных.

Observer и виртуальный DOM

Паттерн Observer не обновляет DOM напрямую. Он лишь сигнализирует о необходимости:

  • Перезапуска эффекта рендера.
  • Генерации нового виртуального DOM.
  • Диффинга и применения минимальных изменений.

Таким образом, Observer отвечает только за реакцию на изменение состояния, а не за отображение.


Практическое значение паттерна

Observer во Vue:

  • Изолирует бизнес-логику от UI.
  • Обеспечивает детерминированные обновления.
  • Минимизирует ручное управление состоянием.
  • Позволяет масштабировать приложение без усложнения кода.

Вся реактивная система Vue — это специализированная, оптимизированная реализация паттерна Observer, адаптированная под особенности JavaScript и компонентной архитектуры.