Реактивность: Vue 3 под капотом
Реактивность: Vue 3 под капотом
reactive() во Vue 3 реализован именно через Proxy. Понимание этого помогает лучше работать с реактивностью и объяснять её поведение.
Упрощённая реализация reactive()
// Трекер зависимостей
const activeEffect = { fn: null }
const deps = new WeakMap() // target → Map<prop → Set<effect>>
function track(target, prop) {
if (!activeEffect.fn) return
if (!deps.has(target)) deps.set(target, new Map())
const targetDeps = deps.get(target)
if (!targetDeps.has(prop)) targetDeps.set(prop, new Set())
targetDeps.get(prop).add(activeEffect.fn)
}
function trigger(target, prop) {
deps.get(target)?.get(prop)?.forEach(fn => fn())
}
// Реактивный объект
function reactive(obj) {
return new Proxy(obj, {
get(target, prop, receiver) {
track(target, prop) // регистрируем зависимость
return Reflect.get(target, prop, receiver)
},
set(target, prop, value, receiver) {
const result = Reflect.set(target, prop, value, receiver)
trigger(target, prop) // уведомляем подписчиков
return result
}
})
}
// Эффект — функция, автоматически перезапускающаяся при изменениях
function effect(fn) {
activeEffect.fn = fn
fn() // первый запуск — собираем зависимости
activeEffect.fn = null
}
Как это работает на практике
const state = reactive({ count: 0, name: 'Алиса' })
// effect автоматически отслеживает зависимости
effect(() => {
console.log(`${state.name}: ${state.count}`)
})
// сразу выводит: "Алиса: 0"
state.count = 1 // автоматически: "Алиса: 1"
state.name = 'Боб' // автоматически: "Боб: 1"
state.count = 5 // автоматически: "Боб: 5"
Именно поэтому во Vue деструктуризация
const { count } = reactive({count: 0}) «теряет» реактивность — count становится примитивным значением, не обёрнутым в Proxy. Решение: toRefs().Почему Proxy лучше Object.defineProperty
Vue 2 использовал Object.defineProperty — и это имело ограничения:
// Vue 2: не обнаруживало новые свойства
const state = reactive({ count: 0 })
state.newProp = 'hello' // ❌ не реактивно!
// Требовалось: Vue.set(state, 'newProp', 'hello')
// Vue 3 с Proxy: любые изменения перехватываются
const state3 = reactive({})
state3.newProp = 'hello' // ✅ реактивно!
Карта зависимостей хранится в
WeakMap(target → ...). WeakMap позволяет сборщику мусора удалить зависимости автоматически, когда оригинальный объект (target) больше не доступен. Это предотвращает утечки памяти при удалении реактивных объектов.