Vue3 Proxy响应式原理与实现详解

Vue3 Proxy响应式原理与实现详解 1. 为什么Vue3选择了Proxy响应式方案在Vue2时代我们使用的是Object.defineProperty来实现响应式系统。这种方式有几个明显的缺陷无法检测对象属性的添加或删除需要使用Vue.set/Vue.delete对数组的变异方法需要特殊处理性能开销随着数据规模线性增长而Proxy是ES6引入的全新特性它可以在对象级别进行拦截提供了更全面的拦截能力const handler { get(target, key) { console.log(获取属性 ${key}) return Reflect.get(target, key) }, set(target, key, value) { console.log(设置属性 ${key} 为 ${value}) return Reflect.set(target, key, value) } } const proxy new Proxy({}, handler)Proxy的优势具体体现在全面拦截可以拦截对象的所有操作包括属性访问、赋值、删除、in操作符等性能优化只在被访问时才会递归处理嵌套对象而不是像Vue2那样一开始就递归遍历所有属性数组处理直接支持数组索引修改和length变化新数据结构支持Map、Set等ES6集合类型2. Proxy响应式核心实现原理2.1 基本响应式实现让我们从最基础的Proxy响应式实现开始function reactiveT extends object(target: T): T { const handler: ProxyHandlerT { get(target, key, receiver) { const res Reflect.get(target, key, receiver) // 如果是对象递归代理 if (typeof res object res ! null) { return reactive(res) } return res }, set(target, key, value, receiver) { const oldValue target[key as keyof T] const result Reflect.set(target, key, value, receiver) // 只有值变化时才触发更新 if (oldValue ! value) { console.log(属性 ${String(key)} 发生变化) } return result } } return new Proxy(target, handler) }这个基础版本已经可以实现简单的响应式功能const state reactive({ count: 0 }) state.count // 控制台会输出变化2.2 依赖收集与触发更新真正的响应式系统需要实现依赖收集和触发更新。我们需要三个核心概念Effect副作用函数当依赖的数据变化时需要重新执行Dep依赖集合存储所有依赖于某个属性的effectTargetMap全局存储映射每个对象到它的属性依赖type Effect () void type Dep SetEffect type TargetMap WeakMapobject, Mapany, Dep const targetMap: TargetMap new WeakMap() let activeEffect: Effect | null null function track(target: object, key: unknown) { 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) } function trigger(target: object, key: unknown) { const depsMap targetMap.get(target) if (!depsMap) return const dep depsMap.get(key) if (dep) { dep.forEach(effect effect()) } }2.3 完整响应式实现现在我们可以完善我们的reactive函数function reactiveT extends object(target: T): T { const handler: ProxyHandlerT { get(target, key, receiver) { const res Reflect.get(target, key, receiver) track(target, key) if (typeof res object res ! null) { return reactive(res) } return res }, set(target, key, value, receiver) { const oldValue target[key as keyof T] const result Reflect.set(target, key, value, receiver) if (oldValue ! value) { trigger(target, key) } return result } } return new Proxy(target, handler) } function effect(fn: Effect) { activeEffect fn fn() activeEffect null }使用示例const state reactive({ count: 0 }) effect(() { console.log(count is: ${state.count}) }) state.count // 会触发effect重新执行3. Ref实现原理在Vue3中ref用于处理基本类型的响应式。让我们实现一个简单的reffunction refT(value: T) { return { get value() { track(this, value) return value }, set value(newVal) { if (value ! newVal) { value newVal trigger(this, value) } } } }ref的核心原理使用getter/setter而不是Proxy通过.value属性访问值同样使用track/trigger进行依赖管理4. 响应式系统的边界情况处理4.1 原始值与代理值const raw {} const proxy reactive(raw) console.log(proxy raw) // false在Vue3中原始对象和响应式代理是不同的对象。这可能导致一些边界情况const obj reactive({}) obj.nested { count: 0 } // 新赋值的对象不会自动变成响应式解决方案是始终使用响应式API创建对象obj.nested reactive({ count: 0 })4.2 数组处理Proxy可以很好地处理数组操作const arr reactive([1, 2, 3]) effect(() { console.log(arr.length) }) arr.push(4) // 会触发effect4.3 集合类型对于Map、Set等集合类型需要特殊处理function reactiveMapT extends Mapany, any(target: T): T { const handler: ProxyHandlerT { get(target, key, receiver) { if (key size) { track(target, size) } const res Reflect.get(target, key, receiver) if (typeof res function) { return function(...args: any[]) { const result res.apply(target, args) trigger(target, size) return result } } return res } } return new Proxy(target, handler) }5. 性能优化技巧5.1 浅层响应式对于大型对象可以使用shallowReactivefunction shallowReactiveT extends object(target: T): T { const handler: ProxyHandlerT { get(target, key, receiver) { track(target, key) return Reflect.get(target, key, receiver) }, set(target, key, value, receiver) { const oldValue target[key as keyof T] const result Reflect.set(target, key, value, receiver) if (oldValue ! value) { trigger(target, key) } return result } } return new Proxy(target, handler) }5.2 只读代理对于不需要修改的数据可以使用readonlyfunction readonlyT extends object(target: T): T { const handler: ProxyHandlerT { get(target, key, receiver) { const res Reflect.get(target, key, receiver) if (typeof res object res ! null) { return readonly(res) } return res }, set() { console.warn(只读对象不可修改) return true } } return new Proxy(target, handler) }5.3 延迟代理对于嵌套对象可以延迟创建代理function lazyReactiveT extends object(target: T): T { const handler: ProxyHandlerT { get(target, key, receiver) { const res Reflect.get(target, key, receiver) track(target, key) if (typeof res object res ! null) { // 只有在被访问时才创建代理 return reactive(res) } return res }, // ...其他trap } return new Proxy(target, handler) }6. 与TypeScript的类型集成为了让我们的响应式库更好地支持TypeScript需要添加适当的类型定义declare const ReactiveMarker: unique symbol interface ReactiveObject { [ReactiveMarker]?: true } function reactiveT extends object(target: T): T ReactiveObject { // 实现... } function isReactive(value: unknown): value is ReactiveObject { return !!(value (value as ReactiveObject)[ReactiveMarker]) }这样可以在TypeScript中区分普通对象和响应式对象const obj reactive({ count: 0 }) if (isReactive(obj)) { // 类型守卫 }7. 完整实现与测试让我们把这些部分组合起来实现一个完整的迷你响应式库// 完整代码见上文各部分实现 // 测试用例 const state reactive({ count: 0, nested: { value: hello }, arr: [1, 2, 3] }) effect(() { console.log(count: ${state.count}, nested: ${state.nested.value}) }) state.count state.nested.value world const map reactive(new Map([[key, value]])) effect(() { console.log(map size: ${map.size}) }) map.set(newKey, newValue)这个实现包含了Vue3响应式系统的核心功能基于Proxy的响应式跟踪嵌套对象的自动代理依赖收集和触发更新基本类型支持(ref)集合类型支持8. 实际应用中的注意事项在实际项目中使用Proxy响应式时需要注意原始对象与代理对象始终使用响应式代理避免混用原始对象性能考量对于大型数据结构考虑使用shallowReactive解构问题解构会丢失响应式连接应该使用toRefs异步更新Vue使用微任务队列批量更新我们的简单实现是同步触发// 解构问题示例 const { count } state // 解构后count不是响应式的 // 解决方案 function toRefsT extends object(proxy: T) { const result: any {} for (const key in proxy) { result[key] { get value() { return proxy[key] }, set value(v) { proxy[key] v } } } return result } const { count } toRefs(state) // 现在count是响应式的9. 与Vue3响应式系统的差异我们实现的简化版与Vue3实际实现的主要区别批量更新Vue3使用调度器(scheduler)实现批量异步更新更完善的类型支持Vue3有更复杂的类型系统集成更多APIVue3提供了readonly、shallowRef、markRaw等更多API内存管理Vue3有更完善的effect清理机制调试支持Vue3提供了onTrack、onTrigger等调试钩子10. 扩展思考基于这个基础实现我们可以进一步扩展计算属性基于effect实现computedwatch API实现数据变化的监听自定义Reactive转换支持自定义对象的响应式转换逻辑SSR支持服务端渲染时的特殊处理// 简单计算属性实现 function computedT(getter: () T) { let value: T let dirty true const runner effect(getter, { lazy: true, scheduler() { dirty true } }) return { get value() { if (dirty) { value runner() dirty false } return value } } }通过这个从零实现的响应式库我们深入理解了Vue3响应式系统的核心原理。Proxy的强大拦截能力使得Vue3的响应式系统比Vue2更加灵活和强大同时也带来了更好的性能表现。