Vue3的响应式系统相比Vue2发生了根本性重构,从Object.defineProperty迁移到Proxy,解决了数组监听限制和新增属性检测等问题。理解Vue3响应式的底层实现链路,对排查组件更新异常、优化渲染性能和编写自定义ref/composable至关重要。本文从源码层面拆解reactive、ref、effect三者的协作机制。
Proxy代理与reactive的实现机制
Vue3使用ES6 Proxy替代Object.defineProperty实现对象代理。Proxy可以拦截13种操作,而defineProperty只能拦截属性读取和设置。对于数组的索引修改、length变更以及Map/Set操作,Proxy都能原生拦截。
reactive函数的核心实现:
const reactiveMap = new WeakMap()
function reactive(target) {
if (typeof target !== 'object' || target === null) {
return target
}
if (target[ReactiveFlags.IS_REACTIVE]) {
return target
}
const existingProxy = reactiveMap.get(target)
if (existingProxy) {
return existingProxy
}
const proxy = new Proxy(target, {
get(target, key, receiver) {
if (key === ReactiveFlags.IS_REACTIVE) {
return true
}
const result = Reflect.get(target, key, receiver)
track(target, key)
if (typeof result === 'object' && result !== null) {
return reactive(result)
}
return result
},
set(target, key, value, receiver) {
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
if (!Object.is(oldValue, value)) {
trigger(target, key, value, oldValue)
}
return result
},
deleteProperty(target, key) {
const hadKey = key in target
const result = Reflect.deleteProperty(target, key)
if (hadKey && result) {
trigger(target, key, undefined, undefined, 'delete')
}
return result
},
has(target, key) {
const result = Reflect.has(target, key)
track(target, key)
return result
},
ownKeys(target) {
track(target, ITERATE_KEY)
return Reflect.ownKeys(target)
}
})
reactiveMap.set(target, proxy)
return proxy
}
关键设计点:reactiveMap使用WeakMap缓存原始对象到代理对象的映射,防止同一对象被多次代理造成内存泄漏。嵌套对象采用懒代理策略——只有访问到该属性时才递归调用reactive,减少初始化开销。
依赖收集系统:track与effect的依赖图
响应式系统的核心是建立属性到effect的依赖映射。当effect内读取了响应式对象的属性时,这个effect被记录为该属性的依赖;当属性变化时,遍历该属性的依赖列表重新执行对应effect。
const targetMap = new WeakMap()
let activeEffect = null
const effectStack = []
function track(target, key) {
if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let dep = depsMap.get(key)
if (!dep) {
dep = new Set()
depsMap.set(key, dep)
}
if (!dep.has(activeEffect)) {
dep.add(activeEffect)
activeEffect.deps.push(dep)
}
}
function trigger(target, key, newValue, oldValue, type) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const effects = new Set()
if (type === 'add' || type === 'delete') {
const iterateEffects = depsMap.get(ITERATE_KEY)
if (iterateEffects) {
iterateEffects.forEach(e => effects.add(e))
}
}
const dep = depsMap.get(key)
if (dep) {
dep.forEach(e => effects.add(e))
}
effects.forEach(effect => {
if (effect !== activeEffect) {
effect.scheduler ? effect.scheduler() : effect.run()
}
})
}
依赖映射使用WeakMap<target, Map<key, Set<effect>>>三层结构。WeakMap的弱引用特性确保target对象被回收时,对应的依赖记录自动清除。
effect函数与调度器实现
effect是响应式系统的执行单元,组件的render函数和watch回调都被包装为effect。Vue3利用scheduler参数将更新推入微任务队列实现异步批处理。
class ReactiveEffect {
constructor(fn, scheduler) {
this.fn = fn
this.scheduler = scheduler
this.deps = []
this.active = true
}
run() {
if (!this.active) {
return this.fn()
}
try {
effectStack.push(this)
activeEffect = this
return this.fn()
} finally {
effectStack.pop()
activeEffect = effectStack[effectStack.length - 1]
}
}
stop() {
if (this.active) {
cleanup(this)
this.active = false
}
}
}
function cleanup(effect) {
effect.deps.forEach(dep => {
dep.delete(effect)
})
effect.deps.length = 0
}
function effect(fn, options = {}) {
const _effect = new ReactiveEffect(fn, options.scheduler)
if (!options.lazy) {
_effect.run()
}
return _effect
}
// 组件更新批处理
const queue = new Set()
let isFlushing = false
function queueJob(job) {
queue.add(job)
if (!isFlushing) {
isFlushing = true
Promise.resolve().then(() => {
queue.forEach(job => job())
queue.clear()
isFlushing = false
})
}
}
cleanup函数在每次effect重新执行前做依赖清理——清除上一次执行时收集的依赖关系,重新收集。这解决了条件分支导致的依赖变化。queueJob使用Promise.resolve().then()将多次同步状态变更合并为一次DOM更新——同一tick内多次修改响应式数据,组件只重新渲染一次。
ref的工作原理与基本类型响应式
Proxy只能代理对象,无法拦截基本类型的属性访问。ref通过对象包装解决这一限制——用.value属性作为代理点。
class RefImpl {
constructor(value) {
this._isRef = true
this._rawValue = value
this._value = typeof value === 'object' && value !== null
? reactive(value)
: value
this.dep = new Set()
}
get value() {
if (activeEffect) {
if (!this.dep.has(activeEffect)) {
this.dep.add(activeEffect)
activeEffect.deps.push(this.dep)
}
}
return this._value
}
set value(newVal) {
if (!Object.is(newVal, this._rawValue)) {
this._rawValue = newVal
this._value = typeof newVal === 'object' && newVal !== null
? reactive(newVal)
: newVal
this.dep.forEach(effect => {
effect.scheduler ? effect.scheduler() : effect.run()
})
}
}
}
ref与reactive的依赖模型不同:reactive的依赖存在全局targetMap中,ref的依赖存在实例自身的dep属性中。proxyRefs实现了模板中ref的自动解包——在template中写{{ count }}而非{{ count.value }}的原因。
computed实现与缓存机制
computed基于effect的lazy选项实现缓存。声明时创建effect但不立即执行,首次访问时才执行并缓存结果。只有依赖的响应式数据变化后,下一次访问才重新计算。
class ComputedRefImpl {
constructor(getter) {
this._isRef = true
this._dirty = true
this._value = undefined
this.dep = new Set()
this.effect = new ReactiveEffect(getter, () => {
if (!this._dirty) {
this._dirty = true
this.dep.forEach(e => {
e.scheduler ? e.scheduler() : e.run()
})
}
})
}
get value() {
if (activeEffect) {
if (!this.dep.has(activeEffect)) {
this.dep.add(activeEffect)
activeEffect.deps.push(this.dep)
}
}
if (this._dirty) {
this._dirty = false
this._value = this.effect.run()
}
return this._value
}
}
核心设计:_dirty作为缓存标记,首次get时为true触发计算,之后为false直接返回缓存值。当computed依赖的reactive数据变化时,触发effect的scheduler,scheduler不执行计算,只将_dirty标记为true并通知依赖该computed的render effect。render effect重新执行时访问computed.value,发现dirty为true才触发重算。这种惰性求值策略避免了不需要的计算。
响应式调试与性能优化实践
排查响应式更新未触发时,使用Vue DevTools的Component Inspector检查依赖列表。如果某个属性未出现在依赖集合中,说明组件render过程中未实际读取该属性。常见原因:在事件处理函数中读取属性不构成依赖(因为事件处理函数不是render effect),或条件分支导致属性未被访问。
对于大型列表渲染,使用shallowRef包装大数组,只在需要时手动触发更新:
import { shallowRef, triggerRef, markRaw } from 'vue'
const bigList = shallowRef([])
function loadData(data) {
bigList.value = data
triggerRef(bigList)
}
// 第三方类实例标记为不需要响应式
const staticConfig = markRaw({
columns: [/* 大量静态数据 */],
options: { /* ... */ }
})
markRaw在对象上添加__v_skip标记,reactive函数检测到该标记时跳过代理。适用于第三方类实例(如Map、Chart实例)和无需响应式的大数据集合。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3-xiang-ying-shi-xi-tong-yuan-li-pou-xi-proxy-dai-li-yu/