- PVSM.RU - https://www.pvsm.ru -
Привет.
Это моя первая статья здесь. Долгое время не решался что-то публиковать, хотя регулярно читал и разбирал материалы других авторов.
Для первой публикации я выбрал тему внутренней оптимизации реактивности во 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’а компонента
Спасибо за внимание.
Автор: wisead
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/reactive/444746
Ссылки в тексте:
[1] мышление: http://www.braintools.ru
[2] Источник: https://habr.com/ru/articles/996052/?utm_source=habrahabr&utm_medium=rss&utm_campaign=996052
Нажмите здесь для печати.