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 — Планировщик эффектов и порядок хуков жизненного цикла опираются на один и тот же механизм обновлений