trackOpBits во Vue 3: как битовые маски ускоряют ReactiveEffect
- суббота, 14 февраля 2026 г. в 00:00:07
Привет, Хабр.
Это моя первая статья здесь. Долгое время не решался что-то публиковать, хотя регулярно читал и разбирал материалы других авторов.
Для первой публикации я выбрал тему внутренней оптимизации реактивности во Vue 3 — trackOpBits и работу ReactiveEffect. Этот механизм почти не заметен при обычной работе с фреймворком, но он напрямую влияет на производительность рендера компонентов и поведение вложенных computed.
В статье разберём, какую проблему решает trackOpBits, как именно он используется внутри системы реактивности и почему эта оптимизация важна в реальных приложениях.
В Vue 3 любая реактивная логика завязана на ReactiveEffect:
рендер компонента
computed
watchEffect
watch
Все они внутри — effect’ы.
Упрощённо:
class ReactiveEffect { fn deps = [] active = true trackOpBits = 0 }
Во время выполнения fn effect становится активным, и все обращения к реактивным данным регистрируются как зависимости.
Посмотрим на реальный сценарий, который происходит в каждом Vue-приложении.
const state = reactive({ price: 100, count: 2, tax: 0.2 }) const total = computed(() => { return state.price * state.count }) const totalWithTax = computed(() => { return total.value * (1 + state.tax) })
А теперь представим компонент:
const Comp = { setup() { return () => { return h('div', totalWithTax.value) } } }
Что реально происходит при первом рендере:
Создаётся render effect компонента
Внутри рендера читается totalWithTax.value
totalWithTax — это computed, у него свой effect
Внутри totalWithTax читается total.value
total — ещё один computed, ещё один effect
Внутри total читаются: state.price, state.count
Итого, мы имеем вложенность effect’ов глубиной 3:
render effect └─ computed(totalWithTax) └─ computed(total) └─ reactive state
Наивная реализация реактивности делала бы следующее:
каждый get: добавляет activeEffect в dep
каждый effect: при новом запуске очищает все deps и пересобирает их заново
При вложенных effect’ах это означает:
повторные добавления одного и того же effect’а
лишние проверки
постоянные cleanup даже там, где зависимости не менялись
На больших деревьях компонентов и сложных computed это быстро становится дорогой операцией.
Во Vue 3 есть глобальный счётчик:
let effectTrackDepth = 0
Каждый раз, когда начинается выполнение effect’а:
effectTrackDepth++
А при завершении — уменьшается.
Это позволяет Vue понимать, на каком уровне вложенности сейчас идёт сбор зависимостей.
trackOpBits — это битовая маска, хранящая информацию о том,
на каких уровнях глубины effect уже был зарегистрирован в зависимостях.
Для текущей глубины вычисляется бит:
const trackOpBit = 1 << effectTrackDepth
Этот бит используется как флаг.
Когда выполняется track(dep):
Vue проверяет: есть ли у effect’а trackOpBit для текущей глубины
Если бит уже установлен: effect не добавляется повторно в dep
Если бита нет: effect добавляется и выставляется бит
if (!(effect.trackOpBits & trackOpBit)) { dep.add(effect) effect.trackOpBits |= trackOpBit }
Таким образом:
один и тот же effect не может быть добавлен дважды
Vue избегает лишних операций при вложенных вычислениях
computed во Vue 3:
ленивые
кешируемые
могут вызываться из других computed и из рендера
Без trackOpBits каждый доступ к .value во вложенных цепочках приводил бы к:
повторному трекингу
очистке зависимостей
лишним аллокациям
С битовой маской:
зависимости собираются один раз на уровень
повторные чтения становятся почти бесплатными
Во Vue 3 есть ограничение на максимальную глубину, где используется битовая оптимизация
(на момент написания — 30 уровней).
После этого Vue аккуратно откатывается к более простой логике трекинга, без битов. Это сделано, чтобы:
избежать переполнения битовой маски
сохранить предсказуемое поведение
На практике в обычных приложениях до этого лимита почти никогда не доходят.
Во Vue 2:
реактивность строилась на Object.defineProperty
не было ReactiveEffect в текущем виде
не было чёткого контроля вложенности эффектов
Архитектура Vue 3 (Proxy + эффекты) позволила:
отслеживать глубину
использовать битовые маски
минимизировать работу GC и аллокации
trackOpBits — пример оптимизации, которая стала возможной только после полной переработки реактивности.
Скорее нет — Vue отлично работает и без этого знания.
Но если вы:
дебажите странные перерендеры
пишете сложные computed
работаете с производительностью
или просто хотите понимать, что происходит под капотом
— знание таких деталей сильно упрощает мышление о поведении фреймворка.
trackOpBits — маленькая, но очень важная часть реактивности Vue 3.
Она позволяет:
эффективно работать с вложенными effect’ами
избежать лишнего трекинга
сделать computed и рендер компонентов действительно быстрыми
Именно такие низкоуровневые решения создают ощущение, что Vue 3 «просто летает», даже в больших приложениях.
Если тема будет интересна — можно отдельно разобрать:
scheduler эффектов
очереди pre / post flush
или жизненный цикл рендер effect’а компонента
Спасибо за внимание.