公司动态
《Vue3 从入门到大神33篇》Vue3 源码详解(三):响应式核心思想——从 Object.defineProperty 到 Proxy
本篇关键词响应式本质、Proxy、依赖收集、effect、track、trigger难度等级⭐⭐⭐⭐☆偏底层但全程带 Demo工程价值读懂这一篇你能手写简易状态管理库、精准定位响应式 Bug、面试秒杀 90% 候选人。一、开篇为什么 Vue3 要抛弃 Object.defineProperty如果你用过 Vue2一定写过这样的代码data() { return { list: [1, 2, 3] } }然后你发现this.list[0] 99 // ❌ 不触发更新 this.list.length 0 // ❌ 不触发更新 delete this.obj.a // ❌ 不触发更新Vue2 的解决方案是set、delete、重写数组方法。这些不是“特性”而是补丁。Vue2 响应式的本质缺陷Object.defineProperty只能做到✅ 劫持已有属性❌ 无法监听新增 / 删除属性❌ 无法监听数组索引 / length❌ 需要递归遍历对象性能差结论Vue2 的响应式是“后天修补出来的”。二、Vue3 的解法Proxy 带来的降维打击Vue3 使用Proxy一句话概括其优势Proxy 是对整个对象的拦截而不是对属性的拦截。Proxy 最直观的 Demo先感性理解const obj { count: 0 } const proxy new Proxy(obj, { get(target, key, receiver) { console.log(读取 ${key}) return Reflect.get(target, key, receiver) }, set(target, key, value, receiver) { console.log(修改 ${key} ${value}) return Reflect.set(target, key, value, receiver) } }) proxy.count // 读取 count proxy.count 1 // 修改 count 1✅ 新增属性能拦截✅ 删除属性能拦截✅ 数组索引能拦截✅ 嵌套对象懒代理后面讲三、响应式系统的灵魂三问源码级思维在阅读 Vue3 响应式源码前必须先回答三个问题谁用了这个数据依赖收集数据变了通知谁依赖触发如何避免无限循环调度控制Vue3 的答案非常优雅概念对应源码依赖收集track()触发更新trigger()副作用函数effect()四、手写 Mini ReactiveVIP 核心环节不先写 Demo直接读源码 劝退。下面我们用不到 30 行代码实现一个最简响应式系统。Step 1存储依赖的数据结构// 存储所有副作用 const targetMap new WeakMap() // 当前正在执行的 effect let activeEffect null设计说明WeakMaptarget, Mapkey, Seteffect为什么用WeakMap防止内存泄漏为什么用Set防止重复收集Step 2track —— 依赖收集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) }一句话解释把“当前正在执行的副作用函数”存到“目标对象的指定属性”下。Step 3trigger —— 触发更新function trigger(target, key) { const depsMap targetMap.get(target) if (!depsMap) return const dep depsMap.get(key) if (dep) { dep.forEach(effect effect()) } }一句话解释找到“目标对象指定属性的所有副作用”挨个执行。Step 4effect —— 副作用容器function effect(fn) { activeEffect fn fn() // 执行一次触发 track activeEffect null }关键点执行fn()时内部读取响应式数据触发get→ 调用track完成依赖收集Step 5reactive —— 创建响应式对象function reactive(obj) { return new Proxy(obj, { get(target, key, receiver) { const result Reflect.get(target, key, receiver) track(target, key) return result }, set(target, key, value, receiver) { const result Reflect.set(target, key, value, receiver) trigger(target, key) return result } }) }Step 6完整 Demo 跑起来 const state reactive({ count: 0 }) effect(() { console.log(count , state.count) }) state.count // 控制台自动打印count 1 state.count 5 // 控制台自动打印count 5✅恭喜你你已经理解了 Vue3 响应式的 80% 核心逻辑。五、对照 Vue3 源码你写的就是简化版源码你现在写的代码本质上就是packages/reactivity/src/ ├── effect.ts ← effect / track / trigger ├── reactive.ts ← reactive() └── baseHandlers.ts ← Proxy handlers源码中的关键对应关系手写Vue3 源码activeEffecteffectStacktrack()track()trigger()trigger()depsDep类型区别只是源码多了调度器、递归代理、边界处理。六、工程视角为什么这样设计VIP 重点1️⃣ 为什么用 WeakMapWeakMap → key 必须是对象好处对象销毁 → 键值自动回收避免手动清理依赖Vue2 的痛点2️⃣ 为什么 effect 要支持嵌套effect(() { effect(() { console.log(state.count) }) })Vue3 使用effectStack 解决effectStack.push(effect) activeEffect effect3️⃣ 为什么需要调度器Scheduler试想state.count state.count你不希望触发两次更新。Vue3 通过queueJobnextTickflushPostFlushCbs实现异步批量更新。下一篇详细讲七、调试实战在源码中验证你的理解回到第二篇搭建的环境在以下位置打断点packages/reactivity/src/effect.ts重点关注track()何时被调用trigger()如何遍历depactiveEffect的变化过程你会发现源码逻辑和你手写的 Mini Reactive 几乎一致。八、面试高频追问收藏级Proxy 一定能监听到所有变化吗不能。Map、Set需要单独处理mutableHandlersvscollectionHandlersVue3 为什么还要 refProxy 无法代理基本类型ref本质是{ value }对象的语法糖。track 和 trigger 一定是同步的吗不一定。计算属性、watch、组件更新都可能被调度延迟。九、本篇总结✅ Vue3 响应式 Proxy WeakMap Effect✅ 核心流程读取收集 → 修改触发✅ 手写 Mini Reactive 是读源码的最短路径✅ 工程中理解调度器、嵌套 effect、内存管理一句话升华Vue3 的响应式不是“魔法”而是一个设计极其精巧的观察者模式 代理模式组合。十、下一篇预告《Vue3 源码详解四reactive 与 ref 源码解析搞懂响应式对象创建过程》