Что представляет собой ReactiveEffect
ReactiveEffect — ключевой объект, отвечающий за исполнение реактивных функций в Vue 3. Каждый раз, когда вы вызываете watch, computed или пишете реактивный setup‑функцию, внутри создаётся экземпляр ReactiveEffect. Он хранит ссылку на пользовательскую функцию, список зависимостей (reactive‑объекты, свойства которых были прочитаны) и набор флагов, управляющих поведением эффекта (например, lazy, scheduler).
Экземпляр создаётся через конструктор:
class ReactiveEffect {
constructor(fn, scheduler = null) {
this.fn = fn // пользовательская реактивная функция
this.scheduler = scheduler // опциональный планировщик
this.deps = [] // массив массивов зависимостей
this.active = true // активен ли эффект
this.parent = undefined // ссылка на родительский эффект
}
}
При первом запуске effect.run() происходит сбор зависимостей: все реактивные свойства, к которым происходит обращение, «запоминаются» в deps. Далее, при изменении любого из этих свойств, система уведомляет соответствующий ReactiveEffect, который либо сразу переисполняет функцию, либо передаёт её в планировщик.
Как происходит сбор зависимостей
Сбор зависимостей реализуется через глобальную переменную activeEffect. Когда вызывается effect.run(), она устанавливается в текущий объект, а после выполнения сбрасывается. При чтении реактивного свойства вызывается track, который проверяет наличие activeEffect. Если эффект активен, свойство добавляется в его список зависимостей, а сам эффект — в список подписчиков свойства.
function track(target, key) {
if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
if (!dep.has(activeEffect)) {
dep.add(activeEffect)
activeEffect.deps.push(dep)
}
}
Таким образом, каждый ReactiveEffect знает, какие dep‑множества (наборы зависимостей) он использует, а каждое dep‑множество хранит ссылки на эффекты, которым необходимо реагировать на изменения.
Роль trackOpBits в оптимизации
Vue 3 вводит битовую маску trackOpBits, позволяющую быстро определять, какие операции трекинга необходимы в текущем контексте. При вложенных эффектах (например, computed внутри watch) каждый уровень получает отдельный бит. При чтении свойства проверяется, установлен ли соответствующий бит в текущем trackOpBit. Если бит уже установлен, повторный track пропускается, что избавляет от избыточных записей в зависимости.
Эта техника особенно полезна в сценариях с большими деревьями реактивных объектов, где одни и те же свойства могут быть прочитаны многократно в разных эффектах. Битовая проверка происходит за O(1) и не требует создания дополнительных структур данных.
Жизненный цикл эффекта: запуск, пауза, очистка
- Запуск (
run) – активируетactiveEffect, сбрасывает текущие зависимости и вызывает пользовательскую функцию. Если эффект уже был запущен, его прежние зависимости удаляются, чтобы избежать «утечек» зависимостей. - Пауза (
stop) – отключает эффект от дальнейшего реагирования. При вызовеstopвсеdep‑множества очищаются от ссылки на эффект, а флагactiveменяется наfalse. Это полезно для отмены наблюдения, когда компонент уничтожается. - Очистка (
cleanup) – внутренний процесс, вызываемый как изrun, так и изstop. Перебирает массивdepsи удаляет текущийReactiveEffectиз каждогоdep. После очистки массивdepsобнуляется.
Эти шаги гарантируют, что каждый эффект содержит актуальный набор зависимостей и не реагирует на изменения, которые больше не интересуют его.
Практические детали и отладка
- Планировщик (
scheduler) – позволяет переопределить момент исполнения эффекта. Вместо немедленного вызова функции,schedulerполучает эффект в очередь, где можно объединять несколько обновлений, использоватьrequestAnimationFrameили интегрировать собственные приоритеты. - Ленивая инициализация –
computedиспользуетlazy‑флаг. При создании эффекта он не исполняется сразу, а только при первом чтении вычисляемого свойства. После этого результат кэшируется до тех пор, пока не произойдёт изменение одной из зависимостей. - Отладка зависимостей – в режиме разработки можно воспользоваться функцией
effectScopeи методомtrackEffects/triggerEffects, чтобы вывести в консоль список реактивных свойств, связанных с конкретным эффектом. Это помогает выявлять «мёртвые» зависимости и оптимизировать структуру реактивных данных.
Влияние на производительность
ReactiveEffect и система трекинга, построенные вокруг trackOpBits, позволяют Vue 3 достигать высокой эффективности даже в сложных приложениях. Битовые маски снижают количество лишних записей, а централизованное хранение зависимостей в виде Set‑ов гарантирует быстрый доступ и удаление. Планировщик даёт гибкость в управлении частотой рендеров, что особенно важно при работе с анимациями или большими таблицами данных.
В совокупности эти механизмы формируют основу реактивности Vue 3: каждый изменённый кусок состояния автоматически пробуждает только те эффекты, которые действительно зависят от него, без лишних вычислений и без риска «залипания» старых зависимостей. Это делает фреймворк предсказуемым и масштабируемым, позволяя разработчикам сосредоточиться на бизнес‑логике, а не на управлении состоянием.