Vue3 Proxy 响应式原理
Vue3 如何通过 Proxy 实现响应式,以及 track/trigger 机制。
#type / concept
#status / growing
#tech / dev / frame
#resource / vue3
[!info] related notes
- 所属 MOC: Vue MOC
- 前置概念: Proxy 和 Reflect
- 并列概念: Vue2 Object.defineProperty 响应式原理
- 易混淆概念: Vue3 用 Proxy,Vue2 用 Object.defineProperty
- 关系笔记: Vue2 与 Vue3 响应式系统对比, Vue中的ref和reactive, Map, WeakMap, Set, WeakSet, Map vs WeakMap, Set vs WeakSet
Vue3 Proxy 响应式原理
一句话定义
Vue3 用 Proxy 代理整个对象,在运行时拦截 get/set/deleteProperty 等操作,通过 track 收集依赖、trigger 触发更新。
核心内容
Proxy 代理整个对象
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
track(target, key)
const result = Reflect.get(target, key, receiver)
return isObject(result) ? reactive(result) : result
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver)
trigger(target, key)
return result
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key)
trigger(target, key, 'DELETE')
return result
}
})
}
依赖存储结构:WeakMap → Map → Set
const targetMap = new WeakMap()
// 结构:
// targetMap:
// target1 (原始对象) -> Map:
// 'name' -> Set([effect1, effect2])
// 'age' -> Set([effect3])
// target2 -> Map:
// 'count' -> Set([effect4])
track:收集依赖
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)
}
dep.add(activeEffect)
}
trigger:触发更新
function trigger(target, key, type) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const effects = depsMap.get(key)
if (effects) {
effects.forEach(effect => effect())
}
}
effect:副作用函数
function effect(fn) {
const wrappedFn = () => {
activeEffect = wrappedFn
fn()
activeEffect = null
}
wrappedFn() // 首次执行,收集依赖
return wrappedFn
}
边界与易混淆点
为什么配合 Reflect
- 语义标准:
Reflect是与 Proxy 配套的官方 API - 返回值合理:
Reflect.set返回布尔值表示设置是否成功 - 原型链准确:
receiver参数保证 this 指向正确
Vue3 能监听的操作
| 操作 | Proxy 能拦截 |
|---|---|
| 属性读取 | ✅ get |
| 属性赋值 | ✅ set |
| 属性删除 | ✅ deleteProperty |
arr[index] = value | ✅ set |
arr.length = 0 | ✅ set |
Map.set / Map.get | ✅ get / set |
| 新增属性 | ✅ set |
和 Vue2 Object.defineProperty 的区别
- Vue2:初始化时递归每个属性预先劫持
- Vue3:代理整个对象,访问时再处理,惰性更高
ref 为什么还需要
Proxy 只能代理对象,不能代理原始值。ref 把基本类型包装成对象:
const count = ref(0)
// 内部类似:
// { value: 0 },然后对 value 做响应式处理