The implementation mechanism of reactive and ref
In Vue 3's reactivity system, reactive
and ref
are core APIs designed to make objects and primitive values reactive, respectively. Both rely on Proxy and dependency tracking under the hood but exhibit significant differences in usage scenarios and internal implementations.
Implementation Mechanism of reactive
reactive
uses a Proxy to intercept property access (get) and modification (set) operations on the target object. When a property is accessed, the track
function collects dependencies; when a property is modified, the trigger
function notifies updates. Here’s a simplified implementation:
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
track(target, key) // Dependency collection
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
if (oldValue !== value) {
trigger(target, key) // Trigger updates
}
return result
}
})
}
Deep reactivity is achieved through recursion:
function reactive(target) {
if (isObject(target)) {
Object.entries(target).forEach(([key, val]) => {
if (isObject(val)) target[key] = reactive(val)
})
}
return /* Proxy logic */
}
Special handling for arrays involves intercepting methods like push
:
const arrayInstrumentations = {
push(...args) {
const res = Array.prototype.push.apply(this, args)
trigger(this, 'length') // Manually trigger length change
return res
}
}
Implementation Mechanism of ref
ref
wraps primitive values in an object, using the value
property to access the actual value. Its core implementation is as follows:
function ref(value) {
return {
get value() {
track(this, 'value') // Collect dependencies
return value
},
set value(newVal) {
if (newVal !== value) {
value = newVal
trigger(this, 'value') // Trigger updates
}
}
}
}
Optimizations exist for object types:
function ref(value) {
return isObject(value)
? reactive(value) // Directly convert to reactive
: createRef(value)
}
Automatic unwrapping in templates is achieved during compilation:
<script setup>
const count = ref(0)
</script>
<template>
<!-- Compiled as count.value -->
<button @click="count++">{{ count }}</button>
</template>
Comparison of Differences
Performance-wise, ref
is more efficient for primitive values:
// Create 10,000 reactive variables
const refs = Array(10000).fill(0).map(() => ref(0)) // Faster
const reactives = Array(10000).fill(0).map(() => reactive({ value: 0 }))
Type system support differs significantly:
const numRef = ref<number>(0) // Explicit type
const objReactive = reactive<{ foo: string }>({ foo: 'bar' }) // Automatic inference
Behavior with destructuring:
const state = reactive({ x: 1, y: 2 })
const { x } = state // Loses reactivity
const xRef = toRef(state, 'x') // Maintains reactivity
Underlying Dependency Tracking System
The track
function establishes dependency relationships:
const targetMap = new WeakMap()
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()))
dep.add(activeEffect)
}
The trigger
function triggers updates:
function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const effects = depsMap.get(key)
effects && effects.forEach(effect => effect())
}
Handling Special Scenarios
readonly
shares part of the logic:
function readonly(target) {
return new Proxy(target, {
get: createGetter(true),
set(target, key) {
console.warn(`Set operation on key "${key}" failed: target is readonly.`)
return true
}
})
}
Shallow reactivity with shallowReactive
:
function shallowReactive(target) {
return new Proxy(target, {
get(target, key) {
track(target, key)
const res = Reflect.get(target, key)
return isObject(res) ? res : reactive(res) // No recursive handling
}
})
}
Evolution of Reactivity APIs
Reactivity Transform introduced in Vue 3.3:
// Before compilation
let count = $ref(0)
function increment() {
count++
}
// After compilation
let count = ref(0)
function increment() {
count.value++
}
Integration with the Composition API:
function useCounter() {
const count = ref(0)
const double = computed(() => count.value * 2)
return { count, double }
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:源码调试环境的搭建
下一篇:依赖收集与追踪的详细流程