一、背景:Vue3 代理体系的核心意义
Vue3 的响应式系统基于 Proxy 重构,而整套系统的核心入口只有两个:
reactive(obj)readonly(obj)
它们能工作,是因为 Vue 内部构造了四组极其精妙的处理器(handlers):
mutableHandlers------ reactive 的处理器readonlyHandlers------ readonly 的处理器shallowReactiveHandlers------ 浅层 reactiveshallowReadonlyHandlers------ 浅层 readonly
这些 handler 就是真正拦截对象所有行为(get/set/delete/has/ownKeys)的核心。
全文将逐段解析你提供的这段源码。
二、整体设计图示(关键结构图)
scss
┌───────────────────────────────────────┐
│ reactive(target) │
│ │ │
│ ▼ │
│ new Proxy(target, mutableHandlers) →───▶ 触发 getter / setter / track / trigger
│ │
│ readonly(target) │
│ ▼ │
│ new Proxy(target, readonlyHandlers) ───▶ 拦截写入 + 告警
└───────────────────────────────────────┘
进一步细分 shallow:
bash
reactive → mutableHandlers
shallowReactive → shallowReactiveHandlers
readonly → readonlyHandlers
shallowReadonly → shallowReadonlyHandlers
每个 handler 都继承自 BaseReactiveHandler(除了 readonly 额外封锁写入)。
三、源码逐段解析(逐行注释 + 原理)
以下直接解析你发的源码。
3.1 import 区域
typescript
import {
type Target,
isReadonly,
isShallow,
reactive,
reactiveMap,
readonly,
readonlyMap,
shallowReactiveMap,
shallowReadonlyMap,
toRaw,
} from './reactive'
作用解析:
reactiveMap/readonlyMap:WeakMap,缓存 proxy → rawtoRaw:从代理中取原始对象isReadonly/isShallow:元数据状态标记
3.2 arrayInstrumentations
Array 的特殊行为,比如:
pushincludesindexOf
这些需要特殊处理,以避免读写重复触发依赖。
3.3 一组常量与工具函数
go
const isNonTrackableKeys = makeMap(`__proto__,__v_isRef,__isVue`)
这些 key 访问时 不需要 track,避免无意义的依赖。
javascript
const builtInSymbols = new Set(
Object.getOwnPropertyNames(Symbol)
.filter(key => key !== 'arguments' && key !== 'caller')
.map(key => Symbol[key])
.filter(isSymbol),
)
这段代码用于过滤所有 内建 Symbol,例如:
Symbol.iteratorSymbol.toStringTag
Vue 不会对它们进行 track,否则会污染依赖系统。
3.4 hasOwnProperty 重写版本
vbnet
function hasOwnProperty(this: object, key: unknown) {
if (!isSymbol(key)) key = String(key)
const obj = toRaw(this)
track(obj, TrackOpTypes.HAS, key)
return obj.hasOwnProperty(key as string)
}
关键点
- hasOwnProperty 被代理后需要 track
- this 始终指向 raw,而不是 proxy
3.5 BaseReactiveHandler 核心:get 拦截器
源码如下(重点高亮注释版):
kotlin
class BaseReactiveHandler implements ProxyHandler<Target> {
constructor(
protected readonly _isReadonly = false,
protected readonly _isShallow = false,
) {}
get(target, key, receiver) {
// 1. 读取内部标识
if (key === ReactiveFlags.SKIP) return target[ReactiveFlags.SKIP]
const isReadonly = this._isReadonly
const isShallow = this._isShallow
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
} else if (key === ReactiveFlags.IS_SHALLOW) {
return isShallow
} else if (key === ReactiveFlags.RAW) {
// 返回原始对象
if (
receiver === (isReadonly ? (isShallow ? shallowReadonlyMap : readonlyMap)
: (isShallow ? shallowReactiveMap : reactiveMap)
).get(target)
||
Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)
) {
return target
}
return
}
const targetIsArray = isArray(target)
// 2. 处理数组方法 instrumentations
if (!isReadonly) {
let fn
if (targetIsArray && (fn = arrayInstrumentations[key])) {
return fn
}
if (key === 'hasOwnProperty') {
return hasOwnProperty
}
}
// 3. 真正的读取
const res = Reflect.get(
target,
key,
isRef(target) ? target : receiver,
)
// 4. 内建 Symbol 或非 track key,不做依赖收集
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res
}
// 5. 依赖收集
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
// 6. 浅层不做递归代理
if (isShallow) {
return res
}
// 7. ref unwrapping
if (isRef(res)) {
const value =
targetIsArray && isIntegerKey(key) ? res : res.value
return isReadonly && isObject(value) ? readonly(value) : value
}
// 8. 对象 → 深层 reactive 或 readonly
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
核心逻辑图示
scss
get()
├── 内部标记? → 返回状态
├── 数组特殊函数? → 包装方法
├── 依赖收集 track()
├── shallow? → 直接返回
├── ref? → 解包
├── object? → 递归代理
└── 返回原值
这是 Vue3 响应式系统最重要的逻辑之一。
四、MutableReactiveHandler:核心 setter
源码(高亮注释版):
scss
set(target, key, value, receiver) {
let oldValue = target[key]
// 1. 非 shallow,需要转 raw
if (!this._isShallow) {
const isOldValueReadonly = isReadonly(oldValue)
if (!isShallow(value) && !isReadonly(value)) {
oldValue = toRaw(oldValue)
value = toRaw(value)
}
// 2. oldValue 是 ref,value 不是 ref
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
if (isOldValueReadonly) {
warn(...)
return true
} else {
oldValue.value = value
return true
}
}
}
// 3. 判断 key 是否已存在
const hadKey = isArray(target)
? Number(key) < target.length
: hasOwn(target, key)
// 4. 正常写入
const result = Reflect.set(
target,
key,
value,
isRef(target) ? target : receiver
)
// 5. 触发 trigger
if (target === toRaw(receiver)) {
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
核心行为
- 支持 ref 自动解包赋值(
ref.value = x) - 新 key → ADD 事件
- 存在的 key 改变 → SET 事件
- shallow 模式不做 deep unwrap
五、ReadonlyReactiveHandler:阻止写入
javascript
set() {
warn("target is readonly")
return true
}
deleteProperty() {
warn("target is readonly")
return true
}
readonly 代理禁止改动,但为了不破坏逻辑,返回 true。
六、最终导出 4 套 handler
arduino
export const mutableHandlers = new MutableReactiveHandler()
export const readonlyHandlers = new ReadonlyReactiveHandler()
export const shallowReactiveHandlers = new MutableReactiveHandler(true)
export const shallowReadonlyHandlers = new ReadonlyReactiveHandler(true)
其中:
- shallow = 是否浅层
- readonly = 是否只读
构成 2×2 的组合。
七、实际示例:执行流程演示
示例 1:普通 reactive
ini
const state = reactive({ a: 1 })
state.a
state.a = 2
步骤:
- 触发 get → track
- 触发 set → trigger
示例 2:ref 自动解包
ini
const r = ref(5)
const obj = reactive({ x: r })
obj.x = 10
console.log(r.value) // 10
set() 判断:
- oldValue 是 ref,value 不是 ref
→ 自动写入 oldValue.value
示例 3:readonly 阻止写入
csharp
const obj = readonly({ x: 1 })
obj.x = 2
// warn: failed: target is readonly
八、扩展:Vue3 响应式代理的优势
- 深层代理 lazy(按需代理)
- ref 自动解包规则
- 数组方法特殊处理
- 避免原型链污染
- 降低依赖数量(内建 Symbol 不 track)
这些共同构成性能优异的响应式系统。
九、潜在问题与注意事项
- shallow 模式不会递归代理
- readonly 并非真正不可修改(可以绕过 Proxy)
- ref 自动解包可能带来语义差异
- 对象引用会导致深层递归代理,过大对象要小心
全文结束
本文部分内容借助 AI 辅助生成,并由作者整理审核。