VUE3解析学习 - HANDLERS 模块
一、handlers 的核心设计目标一句话总结不同数据结构用不同的代理策略做到“最小拦截 精确触发”。响应式系统本质上是两件事依赖收集track读取时记录依赖副作用触发trigger写入时触发更新但不同类型的数据变更方式不同可拦截能力不同因此 Vue 把代理逻辑拆成三套类型变更方式Proxy 能力方案Object属性赋值可完整拦截baseHandlersArray属性 方法方法无法直接拦截重写数组方法Map/Set方法驱动只能拦截 get返回重写方法这不是“复杂”而是对 JavaScript 语义边界的工程妥协。二、Object类继承体系Object 的 handler 在baseHandlers.ts中通过类继承组织1. BaseReactiveHandler抽象基类这是统一入口负责get 拦截ref 解包深/浅响应式递归依赖收集核心职责访问属性 → track → 返回响应式值2. MutableReactiveHandler可变对象这是最常见的 reactive 版本set触发两种操作类型TriggerOpTypes.ADD新增属性TriggerOpTypes.SET修改已有属性区分 ADD / SET 的意义在于新增属性 ≠ 修改属性某些依赖只关心结构变化比如 for...in、Object.keys。deleteProperty触发TriggerOpTypes.DELETEhas用于key in obj依赖收集TrackOpTypes.HASownKeys用于for...in / Object.keys / Reflect.ownKeys依赖收集TrackOpTypes.ITERATE这一步是很多人忽略的关键遍历结构本身也是依赖。3. ReadonlyReactiveHandler只读只读版本直接阻断写操作set → 警告 不执行deleteProperty → 警告 不执行但读取仍然会 track。因为只读不等于“无依赖”。三、Array方法重写策略数组的问题在于大部分变更不是通过属性赋值而是通过方法。例如arr.push() arr.splice() arr.shift()Proxy 无法直接拦截方法调用。Vue3 的方案是在 get trap 中返回“重写版本”的数组方法。arrayInstrumentations.ts返回“重写版本”的方法对象。当访问数组方法时proxy.pushBaseReactiveHandler 的 get trap 会判断是不是数组 是不是被重写的方法如果是→ 返回”重写版本“方法这些方法内部先暂停依赖收集避免死循环调用原始数组方法手动 trigger这就是用函数包装模拟“方法拦截”。四、Map / Set工厂函数体系集合类型比数组更极端所有变更都通过方法。map.set() map.delete() set.add()Proxy 能拦截的只有get因此 Vue 采用纯工厂函数模式而不是类继承。1. 只拦截 getcollectionHandlers 的策略get → 返回重写后的方法和数组类似但更彻底。2. 四种 handler 组合创建导出 4 个全局代理对象mutableCollectionHandlers深响应式shallowCollectionHandlers浅响应式readonlyCollectionHandlers只读shallowReadonlyCollectionHandlers浅只读这些不是手写的而是通过createInstrumentationsGetter(isReadonly, shallow)动态生成。3. createInstrumentations 核心工厂这个工厂函数负责构造重写后的 Map/Set 方法在方法内部访问原始对象关键点ReactiveFlags.RAW → 拿到原始数据避免代理套代理导致的递归问题。五、Track / Trigger 的精细控制这是 Vue 3 响应式系统的灵魂改进之一。读取操作被分为GETHASITERATE写入操作被分为SETADDDELETE它们不是简单的一对一关系。精确触发的意义关键规则并不是所有写操作都应该触发所有依赖。例如GET 依赖只关心 SETITERATE 依赖关心 ADD / DELETEHAS 依赖只关心 key 是否存在如果不区分任何写操作 → 全部 effect 触发这会导致不必要的重渲染性能雪崩副作用风暴Vue 2 的响应式就属于粗粒度触发。