Vue

Реактивность изнутри: proxy, track, trigger

Запись count.value++ магическим образом перерисовывает только тот фрагмент шаблона, где count используется, не трогая остального. Никто не вызывал рендер вручную, никто не подписывался на изменение явно. За этим стоит конкретный механизм: при чтении реактивного свойства Vue запоминает, какой эффект его читал (track), а при записи запускает все эффекты, что от него зависели (trigger). Понимание этой пары снимает большинство загадок реактивности - почему деструктуризация ломает связь, почему reactive не работает с числом и когда нужен ref.

  • Точечная перерисовка: меняется одно поле reactive-объекта, обновляется только зависящий от него участок DOM
  • computed пересчитывается лишь когда меняется то, что он читал, а не на каждый рендер
  • watch срабатывает на изменение конкретного источника, потому что он зарегистрирован как эффект на это значение
  • Отладка 'почему не реагирует': деструктуризация reactive или замена объекта целиком разрывают цепочку track/trigger

Предварительные знания

  • ref и reactive: как создаётся реактивное состояние
  • computed и watch на уровне применения
  • Понимание объектов и примитивов в JavaScript

Что такое эффект

В основе реактивности Vue лежит понятие эффекта - функции, которую система запоминает и перезапускает, когда меняются использованные внутри неё реактивные данные. Функция рендера компонента - это эффект: она читает реактивные значения и при их изменении вызывается заново. computed и watch тоже эффекты. Разработчик обычно эффекты явно не создаёт, но именно они связывают данные с реакцией на них.

Ключевая идея: эффект сам определяет свои зависимости фактом чтения. В примере watchEffect читает только count, поэтому изменение name его не запускает. Зависимости не объявляются заранее - они собираются автоматически в момент выполнения эффекта.

Это отличает Vue от моделей, где зависимости перечисляют руками (как массив зависимостей в useEffect React). Vue собирает их сам через перехват чтения, поэтому забыть зависимость нельзя - что прочитано, на то и подписано.

Как эффект в Vue узнаёт, от каких реактивных данных он зависит?

Proxy перехватывает чтение и запись

reactive оборачивает объект в Proxy - встроенный механизм JavaScript, позволяющий перехватить операции над объектом. Vue ставит две ловушки: get срабатывает при чтении свойства, set - при записи. Это и есть точки, где система вклинивается в обычный доступ к данным, чтобы зафиксировать зависимость и запустить обновление.

Когда внутри эффекта пишут state.count, на самом деле срабатывает ловушка get прокси, которая помимо возврата значения вызывает track. А когда выполняют state.count = 1, срабатывает ловушка set, которая после записи вызывает trigger. Обычный код выглядит как простое чтение и присваивание, но за ним стоит перехват.

Прокси перехватывает доступ именно к свойствам объекта. Поэтому деструктуризация const { count } = state вытаскивает примитивное значение в обход прокси: дальнейшие чтения count идут уже не через ловушку get, и связь теряется. Чтобы сохранить реактивность, применяют toRefs.

Какие операции над объектом перехватывает Proxy в реактивности Vue?

track и trigger: связь и реакция

track и trigger - две половины механизма. track выполняется в ловушке get: он берёт текущий активный эффект (тот, что сейчас выполняется) и записывает его в карту зависимостей конкретного свойства. trigger выполняется в ловушке set: он находит в этой карте все эффекты, подписанные на изменённое свойство, и перезапускает их. Так чтение создаёт связь, а запись её активирует.

Активный эффект - это глобальная ссылка, которую Vue устанавливает перед запуском эффекта и сбрасывает после. Поэтому track в момент чтения точно знает, какой эффект сейчас выполняется. Карта зависимостей хранится как структура объект -> свойство -> множество эффектов, что позволяет точечно перезапускать только нужное.

Точечность здесь и даёт эффективность Vue: при изменении одного свойства trigger перезапускает только эффекты, читавшие именно его. Остальная часть шаблона и другие computed не пересчитываются, потому что в их зависимостях этого свойства нет.

Какую роль играют track и trigger в реактивной системе Vue?

Почему reactive только с объектами

Proxy умеет оборачивать только объекты - перехватывать доступ к свойствам. У примитива (число, строка, булево) свойств в этом смысле нет, и обернуть его в Proxy для отслеживания нельзя. Поэтому reactive принимает только объекты. Для примитива Vue даёт ref: это объект-обёртка с единственным свойством value, чтение и запись которого идут через геттер и сеттер, выполняющие тот же track и trigger.

  • reactive(obj) — Proxy на объекте, перехват чтения/записи свойств. Доступ как obj.field, без .value
  • ref(primitive) — Объект-обёртка с .value, track/trigger через геттер и сеттер. Нужен для примитивов

Отсюда же объясняется ещё одно правило: замена reactive-объекта целиком (state = {...}) разрывает реактивность, потому что новый объект не обёрнут в прежний Proxy и эффекты подписаны на старый. Менять нужно свойства существующего объекта, а не переприсваивать саму переменную.

Практический вывод: примитивное состояние держат в ref, групповое связанное состояние - в reactive, и в composable наружу всегда отдают ref. Тогда деструктуризация результата сохраняет связь через геттер/сеттер value, а не теряет её в обход Proxy.

Почему reactive не работает с примитивом вроде числа, а ref работает?

Связь с другими темами

Этот урок объясняет фундамент, на котором стоят все предыдущие реактивные приёмы:

  • Composables — track и trigger объясняют, почему composable возвращает ref - чтобы связь пережила деструктуризацию
  • ref и reactive — Механизм Proxy под reactive и геттер/сеттер под ref - то, что разбиралось снаружи в базовом уроке

Итог

  • Эффект - функция, которую Vue перезапускает при изменении прочитанных ею реактивных данных (рендер, computed, watch)
  • reactive оборачивает объект в Proxy и перехватывает чтение (get) и запись (set) свойств
  • track при чтении свойства запоминает текущий активный эффект как зависимость этого свойства
  • trigger при записи свойства перезапускает все эффекты, которые были на него подписаны
  • reactive работает только с объектами, потому что Proxy перехватывает доступ к свойствам; для примитивов есть ref с .value

Связанные уроки

  • vue-12-composables — Понимание track и trigger объясняет, почему composable обязан возвращать ref, а не его значение
  • vue-11-lifecycle — Планировщик эффектов и порядок хуков жизненного цикла опираются на один и тот же механизм обновлений
Реактивность изнутри: proxy, track, trigger

0

1

Войти