vue3源码之baseHandlers.ts

baseHandlers.ts

packages/reactivity/src/baseHandlers.ts 是 reactive()/shallowReactive()/readonly()/shallowReadonly() 响应式API的 Proxy 陷阱处理文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// packages/reactivity/src/baseHandlers.ts

export const mutableHandlers: ProxyHandler<object> =
/*@__PURE__*/ new MutableReactiveHandler()

export const readonlyHandlers: ProxyHandler<object> =
/*@__PURE__*/ new ReadonlyReactiveHandler()

export const shallowReactiveHandlers: MutableReactiveHandler =
/*@__PURE__*/ new MutableReactiveHandler(true)

// Props handlers are special in the sense that it should not unwrap top-level
// refs (in order to allow refs to be explicitly passed down), but should
// retain the reactivity of the normal readonly object.
export const shallowReadonlyHandlers: ReadonlyReactiveHandler =
/*@__PURE__*/ new ReadonlyReactiveHandler(true)
  • BaseReactiveHandler 类中封装了 Proxy 中的 handler.get 陷阱。

  • MutableReactiveHandler 类继承 BaseReactiveHandler 类,组成完整的非 readonly 类型对象陷阱,其中包含

    • handler.get 陷阱
    • handler.set 陷阱
    • handler.deleteProperty 陷阱
    • handler.has 陷阱
    • handler.ownkeys 陷阱
  • ReadonlyReactiveHandler 类继承 BaseReactiveHandler 类,组成完整的 readonly 类型对象陷阱,其中包含

    • handler.get 陷阱
    • handler.set 陷阱
    • handler.deleteProperty 陷阱
reactive shallowReactive readonly shallowReadonly
_isReadonly false false true true
_isShallow false true false true
handler.get ✅ ✅ ✅ ✅
handler.set ✅ ✅ ✅ ✅
handler.deleteProperty ✅ ✅ ✅ ✅
handler.has ✅ ✅ ❌ ❌
handler.ownkeys ✅ ✅ ❌ ❌

BaseReactiveHandler

get 陷阱源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// packages/reactivity/src/baseHandlers.ts
class BaseReactiveHandler implements ProxyHandler<Target> {
constructor(
protected readonly _isReadonly = false,
protected readonly _isShallow = false,
) {}

get(target: Target, key: string | symbol, receiver: object): any {
if (key === ReactiveFlags.SKIP) return target[ReactiveFlags.SKIP]

const isReadonly = this._isReadonly,
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) ||
// receiver is not the reactive proxy, but has the same prototype
// this means the receiver is a user proxy of the reactive proxy
Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)
) {
return target
}
// early return undefined
return
}

const targetIsArray = isArray(target)

if (!isReadonly) {
let fn: Function | undefined
if (targetIsArray && (fn = arrayInstrumentations[key])) {
return fn
}
if (key === 'hasOwnProperty') {
return hasOwnProperty
}
}

const res = Reflect.get(
target,
key,
// if this is a proxy wrapping a ref, return methods using the raw ref
// as receiver so that we don't have to call `toRaw` on the ref in all
// its class methods
isRef(target) ? target : receiver,
)

if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res
}

if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}

if (isShallow) {
return res
}

if (isRef(res)) {
// ref unwrapping - skip unwrap for Array + integer key.
const value = targetIsArray && isIntegerKey(key) ? res : res.value
return isReadonly && isObject(value) ? readonly(value) : value
}

if (isObject(res)) {
// Convert returned value into a proxy as well. we do the isObject check
// here to avoid invalid value warning. Also need to lazy access readonly
// and reactive here to avoid circular dependency.
return isReadonly ? readonly(res) : reactive(res)
}

return res
}
}

get ReactiveFlags

1
2
3
4
5
6
7
8
9
10
11
12
// packages/reactivity/src/baseHandlers.ts
if (key === ReactiveFlags.SKIP) return target[ReactiveFlags.SKIP]

const isReadonly = this._isReadonly,
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
}

如果 get 代理对象的 ReactiveFlags.SKIP/ReactiveFlags.IS_REACTIVE/ReactiveFlags.IS_READONLY/ReactiveFlags.IS_SHALLOW 属性,直接返回对应的属性。

reactive() shallowReactive() readonly() shallowReadonly()
ReactiveFlags.SKIP undefined undefined undefined undefined
ReactiveFlags.IS_REACTIVE true true false false
ReactiveFlags.IS_READONLY false false true true
ReactiveFlags.IS_SHALLOW false true false true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const obj1 = reactive<Record<string, unknown>>({})
const obj2 = shallowReactive<Record<string, unknown>>({})
const obj3 = readonly<Record<string, unknown>>({})
const obj4 = shallowReadonly<Record<string, unknown>>({})

;[obj1, obj2, obj3, obj4].forEach((item) => {
console.log([
item[ReactiveFlags.SKIP],
item[ReactiveFlags.IS_REACTIVE],
item[ReactiveFlags.IS_READONLY],
item[ReactiveFlags.IS_SHALLOW],
])
})


// [undefined, true, false, false]
// [undefined, true, false, true]
// [undefined, false, true, false]
// [undefined, false, true, true]


get ReactiveFlags.RAW

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// packages/reactivity/src/baseHandlers.ts
if (key === ReactiveFlags.RAW) {
if (
receiver ===
(isReadonly
? isShallow
? shallowReadonlyMap
: readonlyMap
: isShallow
? shallowReactiveMap
: reactiveMap
).get(target) ||
// receiver is not the reactive proxy, but has the same prototype
// this means the receiver is a user proxy of the reactive proxy
Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)
) {
return target
}
// early return undefined
return
}

如果 get 代理对象的 ReactiveFlags.RAW 属性:

首先根据 isReadonly 和 isShallow 的值,从对应的 WeakMap 中获取代理对象。如果 receiver 等于代理对象,则返回对应的原始对象。

1
2
3
4
5
6
7
8
const original = { foo: 1 }
// 此时 reactiveMap 中存储了 original -> proxy
const proxy = reactive<Record<string, unknown>>(original)

// 命中 receiver === reactiveMap.get(original)
const proxyRaw = proxy[ReactiveFlags.RAW]

// { foo: 1 }

其次比较 Object.getPrototypeOf(userProxy) 和 Object.getPrototypeOf(userProxy)的值。当用户使用 Object.create() 或自定义代理时,receiver 可能不在 Vue 的内部 WeakMap 中,但语义上它仍然是 Vue 代理的“包装”或“扩展”。如果两者相等,也返回对应的原始对象。

备注:假设有一段代码执行 obj.name", obj 不是一个 proxy,且自身不含 name 属性,但是它的原型链上有一个 proxy,那么,那个 proxy 的 get() 处理器会被调用,而此时,obj 会作为 receiver 参数传进来。

正常get 原型链get
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const original = { foo: 1 }
const vueProxy = reactive<Record<string, unknown>>(original)

// 用户创建自己的代理来包装 Vue 的代理
const userProxy = new Proxy(vueProxy, {
get(target, key, receiver) {
// 用户想劫持某些属性的访问
if (key === 'foo') {
console.log('访问 foo')
}
return Reflect.get(target, key, receiver)
},
})

// 此时 receiver === userProxy, reactiveMap.get(target) === vueProxy
// 但 Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver) 为 true
// 所以返回 { foo: 1}
const raw = userProxy[ReactiveFlags.RAW]

console.log('raw', raw) // { foo: 1 }


get arr.fn/hasOwnProperty

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// packages/reactivity/src/general.ts
export const isArray: typeof Array.isArray = Array.isArray

// packages/reactivity/src/baseHandlers.ts
function hasOwnProperty(this: object, key: unknown) {
// #10455 hasOwnProperty may be called with non-string values
if (!isSymbol(key)) key = String(key)
const obj = toRaw(this)
track(obj, TrackOpTypes.HAS, key)
return obj.hasOwnProperty(key as string)
}

const targetIsArray = isArray(target)

if (!isReadonly) {
let fn: Function | undefined
if (targetIsArray && (fn = arrayInstrumentations[key])) {
return fn
}
if (key === 'hasOwnProperty') {
return hasOwnProperty
}
}

处理响应式对象的特定属性访问。

  • 如果 target 是数组,且 key 为数组的方法,返回 arrayInstrumentations 中定义的数组方法(劫持数组方法)。

  • 如果 key 为 hasOwnProperty,触发 track(track(obj, TrackOpTypes.HAS, key)) 依赖收集, 同时将响应式对象转换为原始对象,返回原始对象的 hasOwnProperty 结果。


Reflect.get

1
2
3
4
5
6
7
8
9
// packages/reactivity/src/baseHandlers.ts
const res = Reflect.get(
target,
key,
// if this is a proxy wrapping a ref, return methods using the raw ref
// as receiver so that we don't have to call `toRaw` on the ref in all
// its class methods
isRef(target) ? target : receiver,
)

使用 Reflect.get 方法获取 handler.get 方法的操作结果。


get Symbol/nonTrackableKeys

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// packages/shared/src/makeMap.ts
export function makeMap(str: string): (key: string) => boolean {
const map = Object.create(null)
for (const key of str.split(',')) map[key] = 1
return val => val in map
}

// packages/reactivity/src/general.ts
export const isSymbol = (val: unknown): val is symbol => typeof val === 'symbol'

// packages/reactivity/src/baseHandlers.ts
const isNonTrackableKeys = /*@__PURE__*/ makeMap(`__proto__,__v_isRef,__isVue`)

const builtInSymbols = new Set(
/*@__PURE__*/
Object.getOwnPropertyNames(Symbol)
// ios10.x Object.getOwnPropertyNames(Symbol) can enumerate 'arguments' and 'caller'
// but accessing them on Symbol leads to TypeError because Symbol is a strict mode
// function
.filter(key => key !== 'arguments' && key !== 'caller')
.map(key => Symbol[key as keyof SymbolConstructor])
.filter(isSymbol),
)

if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res
}

const isNonTrackableKeys = /*@__PURE__*/ makeMap(`__proto__,__v_isRef,__isVue`)

如果 key 为 Symbol 类型,且在 builtInSymbols 中,即 key 为 JavaScript 内置的 Symbol 类型,返回 handler.get 结果.

如果 key 不为 Symbol 类型,且 key 为不可跟踪的键: __proto__/__v_isRef/__isVue ,直接返回操作结果。


track()

1
2
3
4
// packages/reactivity/src/baseHandlers.ts
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}

如果为非 readonly 类型对象触发 handler.get 陷阱,触发 track(target, TrackOpTypes.GET, key) 依赖收集。


get shallow

1
2
3
4
// packages/reactivity/src/baseHandlers.ts
if (isShallow) {
return res
}

如果为浅响应式对象触发 handler.get 陷阱,直接返回操作结果。


get 结果为ref

1
2
3
4
5
6
// packages/reactivity/src/baseHandlers.ts
if (isRef(res)) {
// ref unwrapping - skip unwrap for Array + integer key.
const value = targetIsArray && isIntegerKey(key) ? res : res.value
return isReadonly && isObject(value) ? readonly(value) : value
}

处理 res 为 ref 响应式对象情况。

  • 如果 target 是数组,且 key 为整数键,不对 ref 对象进行解包,否则 ref 对象的解包结果。
非数组ref解包取值 数组ref不解包取值
  • 如果为 readonly 类型,且 value 为对象,返回 readonly 类型的解包结果,否则返回 value。
isReadonly && isObject(value)取值 !(isReadonly && isObject(value))取值

get 结果为object

1
2
3
4
5
6
7
// packages/reactivity/src/baseHandlers.ts
if (isObject(res)) {
// Convert returned value into a proxy as well. we do the isObject check
// here to avoid invalid value warning. Also need to lazy access readonly
// and reactive here to avoid circular dependency.
return isReadonly ? readonly(res) : reactive(res)
}

如果 handler.get 结果为对象,且 Handler 为 readonly 类型,返回 readonly 包装结果。
如果 handler.get 结果为对象,且 Handler 不为 readonly 类型,返回 reactive 包装结果。


1
return res

处理其他情况,直接返回操作结果。

MutableReactiveHandler

MutableReactiveHandler源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
class MutableReactiveHandler extends BaseReactiveHandler {
constructor(isShallow = false) {
super(false, isShallow)
}

set(
target: Record<string | symbol, unknown>,
key: string | symbol,
value: unknown,
receiver: object,
): boolean {
let oldValue = target[key]
const isArrayWithIntegerKey = isArray(target) && isIntegerKey(key)
if (!this._isShallow) {
const isOldValueReadonly = isReadonly(oldValue)
if (!isShallow(value) && !isReadonly(value)) {
oldValue = toRaw(oldValue)
value = toRaw(value)
}
if (!isArrayWithIntegerKey && isRef(oldValue) && !isRef(value)) {
if (isOldValueReadonly) {
if (__DEV__) {
warn(
`Set operation on key "${String(key)}" failed: target is readonly.`,
target[key],
)
}
return true
} else {
oldValue.value = value
return true
}
}
} else {
// in shallow mode, objects are set as-is regardless of reactive or not
}

const hadKey = isArrayWithIntegerKey
? Number(key) < target.length
: hasOwn(target, key)
const result = Reflect.set(
target,
key,
value,
isRef(target) ? target : receiver,
)
// don't trigger if target is something up in the prototype chain of original
if (target === toRaw(receiver) && result) {
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}

deleteProperty(
target: Record<string | symbol, unknown>,
key: string | symbol,
): boolean {
const hadKey = hasOwn(target, key)
const oldValue = target[key]
const result = Reflect.deleteProperty(target, key)
if (result && hadKey) {
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
}
return result
}

has(target: Record<string | symbol, unknown>, key: string | symbol): boolean {
const result = Reflect.has(target, key)
if (!isSymbol(key) || !builtInSymbols.has(key)) {
track(target, TrackOpTypes.HAS, key)
}
return result
}

ownKeys(target: Record<string | symbol, unknown>): (string | symbol)[] {
track(
target,
TrackOpTypes.ITERATE,
isArray(target) ? 'length' : ITERATE_KEY,
)
return Reflect.ownKeys(target)
}
}

定义了响应式对象的代理拦截函数,包括 set、deleteProperty、has、ownKeys 四个拦截器。

set 陷阱

handler.set() 会拦截目标对象的以下操作:

  • proxy[key] = value 和 proxy.key = value
  • Object.create(proxy)[key] = value
  • Reflect.set()

handler.set()

set陷阱源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// packages/reactivity/src/baseHandlers.ts
set(
target: Record<string | symbol, unknown>,
key: string | symbol,
value: unknown,
receiver: object,
): boolean {
let oldValue = target[key]
const isArrayWithIntegerKey = isArray(target) && isIntegerKey(key)
if (!this._isShallow) {
const isOldValueReadonly = isReadonly(oldValue)
if (!isShallow(value) && !isReadonly(value)) {
oldValue = toRaw(oldValue)
value = toRaw(value)
}
if (!isArrayWithIntegerKey && isRef(oldValue) && !isRef(value)) {
if (isOldValueReadonly) {
if (__DEV__) {
warn(
`Set operation on key "${String(key)}" failed: target is readonly.`,
target[key],
)
}
return true
} else {
oldValue.value = value
return true
}
}
} else {
// in shallow mode, objects are set as-is regardless of reactive or not
}

const hadKey = isArrayWithIntegerKey
? Number(key) < target.length
: hasOwn(target, key)
const result = Reflect.set(
target,
key,
value,
isRef(target) ? target : receiver,
)
// don't trigger if target is something up in the prototype chain of original
if (target === toRaw(receiver) && result) {
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}

shallow 判断

1
2
3
4
if (!this._isShallow) {
...
}
else {}

判断是否为浅层响应式对象,如果不是浅层响应式对象,执行额外操作。


reactive响应式赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// packages/reactivity/src/constants.ts
export enum ReactiveFlags {
IS_SHALLOW = '__v_isShallow',
}

// packages/reactivity/src/reactive.ts
export function isShallow(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.IS_SHALLOW])
}
export function isReadonly(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
}
export function toRaw<T>(observed: T): T {
const raw = observed && (observed as Target)[ReactiveFlags.RAW]
return raw ? toRaw(raw) : observed
}

// packages/reactivity/src/baseHandlers.ts
if (!isShallow(value) && !isReadonly(value)) {
oldValue = toRaw(oldValue)
value = toRaw(value)
}

如果 value 不是浅层响应式,也不是只读响应式,设置 oldValue 和 value 的值。

因为值可能是响应式对象,所以需要使用 toRaw 函数获取其原始对象。


对象旧值ref解包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// packages/reactivity/src/constants.ts
export enum ReactiveFlags {
IS_READONLY = '__v_isReadonly',
}

// packages/reactivity/src/reactive.ts
export function isReadonly(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
}


// packages/reactivity/src/general.ts
export const isArray: typeof Array.isArray = Array.isArray
export const isString = (val: unknown): val is string => typeof val === 'string'

export const isIntegerKey = (key: unknown): boolean =>
isString(key) &&
key !== 'NaN' &&
key[0] !== '-' &&
'' + parseInt(key, 10) === key

// packages/reactivity/src/baseHandlers.ts
const isArrayWithIntegerKey = isArray(target) && isIntegerKey(key)

const isOldValueReadonly = isReadonly(oldValue)

if (!isArrayWithIntegerKey && isRef(oldValue) && !isRef(value)) {
if (isOldValueReadonly) {
if (__DEV__) {
warn(
`Set operation on key "${String(key)}" failed: target is readonly.`,
target[key],
)
}
return true
} else {
oldValue.value = value
return true
}
}

isArray(target) && isIntegerKey(key) 用于判断 target 是否为数组且调用 set 操作的 key 是否为整数键。并将判断结果赋值给 isArrayWithIntegerKey。

!isArrayWithIntegerKey && isRef(oldValue) && !isRef(value) 如果操作的不是数组键,且旧值 oldValue 是 ref 对象,且新值 value 不是 ref 对象时。

  • 如果 oldValue 是只读响应式,测试环境会触发 warn 函数,提示用户不能对只读响应式属性进行 set 操作并中断后续操作。
  • 如果 oldValue 不是只读响应式,设置 oldValue.value 的值为 value。

该逻辑为 reactive 对象的 ref 自动解包逻辑。


Reflect.set()

1
2
3
4
5
6
const result = Reflect.set(
target,
key,
value,
isRef(target) ? target : receiver,
)

Reflect.set 执行实际赋值,同时将操作结果赋值给 result 。


trigger(ADD/SET)

1
2
3
4
5
6
7
8
9
10
11
const hadKey = isArrayWithIntegerKey
? Number(key) < target.length
: hasOwn(target, key)

if (target === toRaw(receiver) && result) {
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}

hadKey 用于判断 set 的 key 属性是否为 target 中已存在的 key。如果 hadKey 为 true,说明本次 set 操作类型为 TriggerOpTypes.SET,否则为 TriggerOpTypes.ADD。

target === toRaw(receiver) 确保修改的是当前对象自身的属性,而非原型链上的属性,且 set 操作成功。

  • 如果 set 的是新增属性,触发 trigger(TriggerOpTypes.ADD) 。

trigger add()

  • 如果 set 的是已存在的属性,且值有变化,触发 trigger(TriggerOpTypes.SET)。

trigger set()

deleteProperty 陷阱

handler.deleteProperty() 方法用于拦截对对象属性的 delete 操作。

  • delete proxy[key] 和 delete proxy.key
  • Reflect.deleteProperty()

handler.deleteProperty()

deleteProperty 陷阱源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// packages/reactivity/src/general.ts
const hasOwnProperty = Object.prototype.hasOwnProperty
export const hasOwn = (
val: object,
key: string | symbol,
): key is keyof typeof val => hasOwnProperty.call(val, key)

// packages/reactivity/src/baseHandlers.ts
deleteProperty(
target: Record<string | symbol, unknown>,
key: string | symbol,
): boolean {
const hadKey = hasOwn(target, key)
const oldValue = target[key]
const result = Reflect.deleteProperty(target, key)
if (result && hadKey) {
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
}
return result
}
  • hadKey: 通过 Object.prototype.hasOwnProperty 方法判断属性是否存在为对象的直接属性,而不是从原型链继承的属性。
  • oldValue: 删除属性前的属性值。
  • result: 调用原生的 Reflect.deleteProperty 执行实际的删除操作,返回 true/false 表示删除是否成功。

当操作成功且删除的是自有属性时,触发 trigger(TriggerOpTypes.DELETE),通知依赖收集器更新依赖。

触发示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const obj = reactive<Record<string, unknown>>({
name: 'Vue',
age: 18,
})

watchEffect(() => {
// 触发 handler.has 中的依赖收集
const result = 'age' in obj
console.log(result)
})

// 删除属性,触发 handler.deleteProperty 中的依赖更新(trigger)
delete obj.age

// true
// false

has 陷阱

handler.has 钩子可以拦截下面这些操作:

  • key in proxy
  • key in Object.create(proxy)
  • with(proxy) { (key); }
  • Reflect.has()

handler.has()

has 陷阱源码

1
2
3
4
5
6
7
8
// packages/reactivity/src/baseHandlers.ts
has(target: Record<string | symbol, unknown>, key: string | symbol): boolean {
const result = Reflect.has(target, key)
if (!isSymbol(key) || !builtInSymbols.has(key)) {
track(target, TrackOpTypes.HAS, key)
}
return result
}

调用原生的 Reflect.has 执行实际的 in 操作,获取真实的检查结果。判断 key 是否为内置 Symbol 类型,如果不是,则触发 track 方法完成依赖收集。最后返回真实的检查结果。

has 拦截器填补了一个重要的空白:in 操作符本身不依赖属性值,只依赖属性是否存在。如果没有 has 拦截器,删除属性后 in 操作的结果变化无法被响应式系统捕获。

触发示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const obj = reactive<Record<string, unknown>>({
name: 'Vue',
age: 18,
})

watchEffect(() => {
// 触发 handler.has 中的依赖收集
const result = 'age' in obj
console.log(result)
})

// 删除属性, 触发 handler.deleteProperty 中的依赖更新(trigger)
delete obj.age

// true
// false

ownKeys 陷阱

handler.ownKeys() 可以拦截以下操作:

  • Object.getOwnPropertyNames()
  • Object.getOwnPropertySymbols()
  • Object.keys()
  • Reflect.ownKeys()
  • for...in 循环

handler.ownkeys()

ownKeys 陷阱源码

1
2
3
4
5
6
7
8
9
// packages/reactivity/src/baseHandlers.ts
ownKeys(target: Record<string | symbol, unknown>): (string | symbol)[] {
track(
target,
TrackOpTypes.ITERATE,
isArray(target) ? 'length' : ITERATE_KEY,
)
return Reflect.ownKeys(target)
}

当触发 handler.ownKeys() 时,触发 track 方法完成依赖收集,在完成依赖收集后,调用原生的 Reflect.ownKeys 返回真实的属性列表。

触发示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const obj = reactive<Record<string, unknown>>({
name: 'Vue',
age: 18,
})

watchEffect(() => {
// Object.keys 触发 handler.ownKeys 依赖
const keys = Object.keys(obj)
console.log(keys)
})

// 新增属性,触发 handler.set 中的依赖更新(trigger)
obj.hobby = 'reading'

nextTick(() => {
// 删除属性,触发 handler.deleteProperty 中的依赖更新(trigger)
delete obj.name
})

// ['name', 'age']
// ['name', 'age', 'hobby']
// ['age', 'hobby']

ReadonlyReactiveHandler

ReadonlyReactiveHandler 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class ReadonlyReactiveHandler extends BaseReactiveHandler {
constructor(isShallow = false) {
super(true, isShallow)
}

set(target: object, key: string | symbol) {
if (__DEV__) {
warn(
`Set operation on key "${String(key)}" failed: target is readonly.`,
target,
)
}
return true
}

deleteProperty(target: object, key: string | symbol) {
if (__DEV__) {
warn(
`Delete operation on key "${String(key)}" failed: target is readonly.`,
target,
)
}
return true
}
}

set 陷阱

set 陷阱源码

1
2
3
4
5
6
7
8
9
10
// packages/reactivity/src/baseHandlers.ts
set(target: object, key: string | symbol) {
if (__DEV__) {
warn(
`Set operation on key "${String(key)}" failed: target is readonly.`,
target,
)
}
return true
}

对只读响应式对象触发 handler.set 陷阱时,测试环境触发警告,生产环境静默失败处理。

deleteProperty 陷阱

deleteProperty 陷阱源码

1
2
3
4
5
6
7
8
9
deleteProperty(target: object, key: string | symbol) {
if (__DEV__) {
warn(
`Delete operation on key "${String(key)}" failed: target is readonly.`,
target,
)
}
return true
}

对只读响应式对象触发 handler.deleteProperty 陷阱时,测试环境触发警告,生产环境静默失败处理。


vue3源码之baseHandlers.ts
https://www.my-web.cn/vue/vue3源码之baseHandlers/
发布于
2026年8月17日
许可协议