vue3源码之collectionHandlers.ts

packages/reactivity/src/collectionHandler.ts 是 Vue 3 响应式系统的核心文件之一, 导出了 Vue 用来拦截和代理 Map/Set/WeakMap/WeakSet 的 Proxy 处理器。

Map/Set/WeakMap/WeakSet 代理对象的 handler.get 陷阱都是由 createInstrumentationGetter 方法创建的。所有的Handlers 都只有一个get方法,因为对于 Map/WeakMap/Set/WeakSet 取值或者是赋值等操作都是调用其方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {
get: /*@__PURE__*/ createInstrumentationGetter(false, false),
}

export const shallowCollectionHandlers: ProxyHandler<CollectionTypes> = {
get: /*@__PURE__*/ createInstrumentationGetter(false, true),
}

export const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {
get: /*@__PURE__*/ createInstrumentationGetter(true, false),
}

export const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> =
{
get: /*@__PURE__*/ createInstrumentationGetter(true, true),
}

createInstrumentationGetter()

createInstrumentationGetter() 源码

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
function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
const instrumentations = createInstrumentations(isReadonly, shallow)

return (
target: CollectionTypes,
key: string | symbol,
receiver: CollectionTypes,
) => {
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
} else if (key === ReactiveFlags.RAW) {
return target
}

return Reflect.get(
hasOwn(instrumentations, key) && key in target
? instrumentations
: target,
key,
receiver,
)
}
}

处理拦截对象的读取属性操作。根据 key 的情况,返回拦截操作结果。

  • key 为 ReactiveFlags 类型。
  • Reflect.get(instrumentations, key, receiver)
  • Reflect.get(target, key, receiver)

处理 ReactiveFlags 键

1
2
3
4
5
6
7
8
// packages/reactivity/src/collectionHandler.ts
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
} else if (key === ReactiveFlags.RAW) {
return target
}

如果 key 为 ReactiveFlags 类型,返回对应的属性值。

不同于 baseHandlers 中的 ReactiveFlags 键,这里只处理了 ReactiveFlags.IS_REACTIVE、ReactiveFlags.IS_READONLY、ReactiveFlags.RAW 三个键,并没有处理 ReactiveFlags.IS_SHALLOW 键。所以通过 isShallow() 方法检查时,无法正确处理,返回的都是 false。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const proxy = reactive<Map<string, any>>(new Map([['age', 18]]))
const proxy2 = shallowReactive<Map<string, any>>(new Map([['age', 18]]))
const proxy3 = readonly<Map<string, any>>(new Map([['age', 18]]))
const proxy4 = shallowReadonly<Map<string, any>>(new Map([['age', 18]]))

;[proxy, proxy2, proxy3, proxy4].forEach((item) => {
console.log([
item[ReactiveFlags.IS_REACTIVE as unknown as keyof typeof item],
item[ReactiveFlags.IS_READONLY as unknown as keyof typeof item],
item[ReactiveFlags.IS_SHALLOW as unknown as keyof typeof item],
item[ReactiveFlags.RAW as unknown as keyof typeof item],
isShallow(item),
])
})


// [true, false, undefined, Map(1), false]
// [true, false, undefined, Map(1), false]
// [false, true, undefined, Map(1), false]
// [false, true, undefined, Map(1), false]

处理其它键

1
2
3
4
5
6
7
8
9
10
// packages/reactivity/src/collectionHandlers.ts
const instrumentations = createInstrumentations(isReadonly, shallow)

return Reflect.get(
hasOwn(instrumentations, key) && key in target
? instrumentations
: target,
key,
receiver,
)

如果 key 在 instrumentations 对象自有属性中,且 key 在 target 对象中,handler.get 陷阱的目标对象设置为 instrumentations 对象,否则设置为 target 对象。

返回 Reflect.get 方法获取代理对象的属性值。

该操作主要是为了劫持 Map/Set/WeakMap/WeakSet 代理对象特定的属性访问(get()/has()/forEach()/add()/set()/delete()/clear()/keys()/values()/entries()/Symbol.iterator),并触发依赖收集或触发响应式系统的行为。

createInstrumentations

createInstrumentations() 源码

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
function createInstrumentations(
readonly: boolean,
shallow: boolean,
): Instrumentations {
const instrumentations: Instrumentations = {
get(this: MapTypes, key: unknown) {
// #1772: readonly(reactive(Map)) should return readonly + reactive version
// of the value
const target = this[ReactiveFlags.RAW]
const rawTarget = toRaw(target)
const rawKey = toRaw(key)
if (!readonly) {
if (hasChanged(key, rawKey)) {
track(rawTarget, TrackOpTypes.GET, key)
}
track(rawTarget, TrackOpTypes.GET, rawKey)
}
const { has } = getProto(rawTarget)
const wrap = shallow ? toShallow : readonly ? toReadonly : toReactive
if (has.call(rawTarget, key)) {
return wrap(target.get(key))
} else if (has.call(rawTarget, rawKey)) {
return wrap(target.get(rawKey))
} else if (target !== rawTarget) {
// #3602 readonly(reactive(Map))
// ensure that the nested reactive `Map` can do tracking for itself
target.get(key)
}
},
get size() {
const target = (this as unknown as IterableCollections)[ReactiveFlags.RAW]
!readonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
return target.size
},
has(this: CollectionTypes, key: unknown): boolean {
const target = this[ReactiveFlags.RAW]
const rawTarget = toRaw(target)
const rawKey = toRaw(key)
if (!readonly) {
if (hasChanged(key, rawKey)) {
track(rawTarget, TrackOpTypes.HAS, key)
}
track(rawTarget, TrackOpTypes.HAS, rawKey)
}
return key === rawKey
? target.has(key)
: target.has(key) || target.has(rawKey)
},
forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {
const observed = this
const target = observed[ReactiveFlags.RAW]
const rawTarget = toRaw(target)
const wrap = shallow ? toShallow : readonly ? toReadonly : toReactive
!readonly && track(rawTarget, TrackOpTypes.ITERATE, ITERATE_KEY)
return target.forEach((value: unknown, key: unknown) => {
// important: make sure the callback is
// 1. invoked with the reactive map as `this` and 3rd arg
// 2. the value received should be a corresponding reactive/readonly.
return callback.call(thisArg, wrap(value), wrap(key), observed)
})
},
}

extend(
instrumentations,
readonly
? {
add: createReadonlyMethod(TriggerOpTypes.ADD),
set: createReadonlyMethod(TriggerOpTypes.SET),
delete: createReadonlyMethod(TriggerOpTypes.DELETE),
clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
}
: {
add(this: SetTypes, value: unknown) {
const target = toRaw(this)
const proto = getProto(target)
const rawValue = toRaw(value)
const valueToAdd =
!shallow && !isShallow(value) && !isReadonly(value)
? rawValue
: value
const hadKey =
proto.has.call(target, valueToAdd) ||
(hasChanged(value, valueToAdd) &&
proto.has.call(target, value)) ||
(hasChanged(rawValue, valueToAdd) &&
proto.has.call(target, rawValue))
if (!hadKey) {
target.add(valueToAdd)
trigger(target, TriggerOpTypes.ADD, valueToAdd, valueToAdd)
}
return this
},
set(this: MapTypes, key: unknown, value: unknown) {
if (!shallow && !isShallow(value) && !isReadonly(value)) {
value = toRaw(value)
}
const target = toRaw(this)
const { has, get } = getProto(target)

let hadKey = has.call(target, key)
if (!hadKey) {
key = toRaw(key)
hadKey = has.call(target, key)
} else if (__DEV__) {
checkIdentityKeys(target, has, key)
}

const oldValue = get.call(target, key)
target.set(key, value)
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
return this
},
delete(this: CollectionTypes, key: unknown) {
const target = toRaw(this)
const { has, get } = getProto(target)
let hadKey = has.call(target, key)
if (!hadKey) {
key = toRaw(key)
hadKey = has.call(target, key)
} else if (__DEV__) {
checkIdentityKeys(target, has, key)
}

const oldValue = get ? get.call(target, key) : undefined
// forward the operation before queueing reactions
const result = target.delete(key)
if (hadKey) {
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
}
return result
},
clear(this: IterableCollections) {
const target = toRaw(this)
const hadItems = target.size !== 0
const oldTarget = __DEV__
? isMap(target)
? new Map(target)
: new Set(target)
: undefined
// forward the operation before queueing reactions
const result = target.clear()
if (hadItems) {
trigger(
target,
TriggerOpTypes.CLEAR,
undefined,
undefined,
oldTarget,
)
}
return result
},
},
)

const iteratorMethods = [
'keys',
'values',
'entries',
Symbol.iterator,
] as const

iteratorMethods.forEach(method => {
instrumentations[method] = createIterableMethod(method, readonly, shallow)
})

return instrumentations
}

Map/Set/WeakMap/WeakSet 的 get()/has()/forEach()/add()/set()/delete()/clear()/keys()/values()/entries()/Symbol.iterator 行为封装。

get()

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
get(this: MapTypes, key: unknown) {
// #1772: readonly(reactive(Map)) should return readonly + reactive version
// of the value
const target = this[ReactiveFlags.RAW]
const rawTarget = toRaw(target)
const rawKey = toRaw(key)
if (!readonly) {
if (hasChanged(key, rawKey)) {
track(rawTarget, TrackOpTypes.GET, key)
}
track(rawTarget, TrackOpTypes.GET, rawKey)
}
const { has } = getProto(rawTarget)
const wrap = shallow ? toShallow : readonly ? toReadonly : toReactive
if (has.call(rawTarget, key)) {
return wrap(target.get(key))
} else if (has.call(rawTarget, rawKey)) {
return wrap(target.get(rawKey))
} else if (target !== rawTarget) {
// #3602 readonly(reactive(Map))
// ensure that the nested reactive `Map` can do tracking for itself
target.get(key)
}
}

这段代码是 Vue 3 中 reactive 和 readonly 对 Map 类型进行代理时的 GET 拦截器。它负责处理 map.get(key) 操作,同时兼顾响应式追踪、只读模式、浅层响应式以及边界情况。


依赖收集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// packages/reactive/src/reactive.ts
export function toRaw<T>(observed: T): T {
const raw = observed && (observed as Target)[ReactiveFlags.RAW]
return raw ? toRaw(raw) : observed
}

// packages/reactive/src/general.ts
export const hasChanged = (value: any, oldValue: any): boolean =>
!Object.is(value, oldValue)

// packages/reactive/src/collectionHandlers.ts
const rawTarget = toRaw(target)
const rawKey = toRaw(key)
if (!readonly) {
if (hasChanged(key, rawKey)) {
track(rawTarget, TrackOpTypes.GET, key)
}
track(rawTarget, TrackOpTypes.GET, rawKey)
}

判断 Handler 是否为只读模式,如果不是,调用 get 方法时触发依赖收集,依赖的 key 值为原始值 rawKey。

如果 Object.is(key, rawKey) 为 false,说明 key 是代理值,同时触发代理值的依赖收集。


处理存在的key

1
2
3
4
5
6
7
8
// packages/reactive/src/collectionHandlers.ts
const rawTarget = toRaw(target)
const rawKey = toRaw(key)
const { has } = getProto(rawTarget)
const wrap = shallow ? toShallow : readonly ? toReadonly : toReactive
if (has.call(rawTarget, key)) {
return wrap(target.get(key))
}

获取 Map 原型链上的 get 方法,如果原始值中存在以 key 为键的项,直接返回对应的 value。

场景示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const label = { label: 'age' }

const labelProxy = reactive(label)

const mapProxy = reactive(
new Map([
// 以label原始值为键
[label, 18],
// 以label代理值为键
[labelProxy, 20],
]),
)

// 以label原始值为键获取值
// 满足has.call(rawTarget, key)
// 直接返回值: 18
const age = mapProxy.get(label)
// 以label代理值为键获取值
// 满足has.call(rawTarget, key)
// 直接返回值: 20
const age2 = mapProxy.get(labelProxy)


处理key为代理值

1
2
3
4
// packages/reactive/src/collectionHandlers.ts
else if (has.call(rawTarget, rawKey)) {
return wrap(target.get(rawKey))
}

因为 Map 可能存储时用的是原始对象,但用户访问时传入了它的代理对象,再次使用 rawKey 判断 Map 中是否存在对应的项。

场景示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const label = { label: 'age' }

const labelProxy = reactive(label)

const mapProxy = reactive(
new Map([
// 以label原始值为键
[label, 19],
]),
)

// 以label代理值为键获取值
// 不满足has.call(rawTarget, key)
// 但满足has.call(rawTarget, rawKey)
// 直接返回值: 19
const age = mapProxy.get(labelProxy)


处理嵌套的响应式 Map

1
2
3
4
5
6
// packages/reactive/src/collectionHandlers.ts
else if (target !== rawTarget) {
// #3602 readonly(reactive(Map))
// ensure that the nested reactive `Map` can do tracking for itself
target.get(key)
}

触发内部嵌套的 reactive(Map) 自身的 GET 拦截器,让它可以执行自己的 track 逻辑,确保响应式追踪不会丢失。即使最外层是只读的,内层的 reactive Map 依然需要收集依赖,以便在数据变化时触发更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const label = { label: 'age' }

const map = reactive(new Map())
const roMap = readonly(map)

watchEffect(() => {
// 此时 target 为 Proxy(Map)
// rawTarget 为 Map()
// 调用 target.get 触发依赖收集
const roItem = roMap.get(label)
console.log('Watch effect ro item:', roItem)
})

// 修改值,触发 watchEffect 执行
map.set(label, { age: 18 })

get size()

1
2
3
4
5
6
// packages/reactive/src/collectionHandlers.ts
get size() {
const target = (this as unknown as IterableCollections)[ReactiveFlags.RAW]
!readonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
return target.size
}

Map/Set 的 size 属性拦截器,返回原始值的 size。当集合执行 set、delete、add、clear 等操作时,size 都会变化。

  • 如果 Handler 为只读模式,直接返回原始值的 size。
  • 如果 Handler 为非只读模式,触发依赖收集。

使用示例

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
const map = reactive<Map<string, any>>(new Map([['age', 10]]))

watchEffect(() => {
// size 依赖收集
const size = map.size
console.log('size', size)
})

// 添加值,触发 watchEffect 执行
setTimeout(() => {
map.set('name', 'xixi')
})
// 删除值,触发 watchEffect 执行
setTimeout(() => {
map.delete('age')
})

// 清空值,触发 watchEffect 执行
setTimeout(() => {
map.clear()
})

// size 1
// size 2
// size 1
// size 0

has()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// packages/reactive/src/collectionHandlers.ts
has(this: CollectionTypes, key: unknown): boolean {
const target = this[ReactiveFlags.RAW]
const rawTarget = toRaw(target)
const rawKey = toRaw(key)
if (!readonly) {
if (hasChanged(key, rawKey)) {
track(rawTarget, TrackOpTypes.HAS, key)
}
track(rawTarget, TrackOpTypes.HAS, rawKey)
}
return key === rawKey
? target.has(key)
: target.has(key) || target.has(rawKey)
}

Map/Set 的 has 属性拦截器,返回。指示具有指定键的元素是否存在。

当 key 为代理值时,检查原始值和代理值是否存在。存在其一即可。

场景示例

1
2
3
4
5
6
7
8
9
const labelObj = { label: 'age' }
const labelProxy = reactive(labelObj)

const map = reactive<Map<unknown, any>>(new Map([[labelObj, 10]]))

// 当执行 has 方法时,Map 中并有 labelProxy 键
// key 为 代理对象,rawKey 为 原始对象,key !== rawKey
// 但满足 target.has(rawKey),此时返回 true
const val = map.has(labelProxy)

返回结果使用三元运算符和直接返回 target.has(key) || target.has(rawKey) 逻辑结果完全相同,但使用三元运算符是出于性能优化的考虑。

当 key === rawKey 时

  • 如果键相同 → 只需检查一次
  • 如果键不同(代理 vs 原始) → 需要检查两种可能

forEach()

1
2
3
4
5
6
7
8
9
10
11
12
13
forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {
const observed = this
const target = observed[ReactiveFlags.RAW]
const rawTarget = toRaw(target)
const wrap = shallow ? toShallow : readonly ? toReadonly : toReactive
!readonly && track(rawTarget, TrackOpTypes.ITERATE, ITERATE_KEY)
return target.forEach((value: unknown, key: unknown) => {
// important: make sure the callback is
// 1. invoked with the reactive map as `this` and 3rd arg
// 2. the value received should be a corresponding reactive/readonly.
return callback.call(thisArg, wrap(value), wrap(key), observed)
})
}

Map/Set 的 forEach 属性拦截器。按插入顺序对集合中的每个键/值对执行一次提供的函数。

用户调用 map.forEach(callback) 时,对每项键/值对执行 callback.call(thisArg, wrap(value), wrap(key), observed) 包装。确保回调中的值能正确响应式追踪。

wrap(value) 只在迭代到该元素时才执行,如果用户提前退出迭代(如条件判断),不会包装未访问的元素。

场景示例

1
2
3
4
5
6
7
8
9
10
11
12
13
const labelObj = { label: 'age' }
const valueObje = { value: 18 }

const map = reactive<Map<unknown, any>>(new Map([[labelObj, valueObje]]))

map.forEach((value, key, map) => {
// valueObje 代理值
const _value = value
// labelObj 代理值
const _key = key
// map 代理值
const _map = map
})

extend()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
extend(
instrumentations,
readonly
? {
add: createReadonlyMethod(TriggerOpTypes.ADD),
set: createReadonlyMethod(TriggerOpTypes.SET),
delete: createReadonlyMethod(TriggerOpTypes.DELETE),
clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
}
: {
add(this: SetTypes, value: unknown) {
...
},
set(this: MapTypes, key: unknown, value: unknown) {
...
},
delete(this: CollectionTypes, key: unknown) {
...
},
clear(this: IterableCollections) {
...
},
},
)

根据是否是只读模式,添加不同的 add/set/delete/clear 属性方法行为。

  • 如果 Handler 为只读模式,执行以上属性方法时,不触发数据变化。测试环境并且显示相关的warn 警告
  • 如果 Handler 不为只读模式,执行以上属性方法时,并根据情况触发相应的响应式追踪。

!readonly add()

add() 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
add(this: SetTypes, value: unknown) {
const target = toRaw(this)
const proto = getProto(target)
const rawValue = toRaw(value)
const valueToAdd =
!shallow && !isShallow(value) && !isReadonly(value)
? rawValue
: value
const hadKey =
proto.has.call(target, valueToAdd) ||
(hasChanged(value, valueToAdd) &&
proto.has.call(target, value)) ||
(hasChanged(rawValue, valueToAdd) &&
proto.has.call(target, rawValue))
if (!hadKey) {
target.add(valueToAdd)
trigger(target, TriggerOpTypes.ADD, valueToAdd, valueToAdd)
}
return this
}

Set 的 add 属性方法拦截器。如果该对象中没有具有相同值的元素,在该集合中插入一个具有指定值的新元素。


转换value值

1
2
3
4
5
6
// packages/reactive/src/collectionHandlers.ts
const rawValue = toRaw(value)
const valueToAdd =
!shallow && !isShallow(value) && !isReadonly(value)
? rawValue
: value

获取 add 方法操作的 value 值:

  • 如果 Handler 不是浅层数据结构模式(!shallow),且 value 不是只读模式(!readonly)、浅层数据结构模式(isShallow),返回 value 的原始值。
  • 否则,返回 value 本身。

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const ageInfo = reactive({ value: 18 })
const nameInfo = shallowReactive({ value: 'xixi' })
const hobbyInfo = readonly({ value: 'reading' })
const sexInfo = shallowReadonly({ value: 'male' })

const proxy = reactive(new Set())

// 添加 ageInfo 的原始值
proxy.add(ageInfo)
// 添加 nameInfo 的代理值
proxy.add(nameInfo)
// 添加 hobbyInfo 的代理值
proxy.add(hobbyInfo)
// 添加 sexInfo 的代理值
proxy.add(sexInfo)
// 添加正常对象
proxy.add({
height: 170,
})


add 执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// packages/reactive/src/collectionHandlers.ts
const target = toRaw(this)
const proto = getProto(target)

const hadKey =
proto.has.call(target, valueToAdd) ||
(hasChanged(value, valueToAdd) &&
proto.has.call(target, value)) ||
(hasChanged(rawValue, valueToAdd) &&
proto.has.call(target, rawValue))
if (!hadKey) {
target.add(valueToAdd)
trigger(target, TriggerOpTypes.ADD, valueToAdd, valueToAdd)
}

判断 Set 中是否已存在重复的值,如果不存在,调用 Set.add 方法添加值。并通过 trigger(target, TriggerOpTypes.ADD, valueToAdd, valueToAdd) 触发响应式更新。

  • 情况1: proto.has.call(target, valueToAdd)

示例1

1
2
3
4
5
6
7
8
9
10
11
const ageInfo = { value: 18 }
const proxy = reactive(new Set())

// 第一次添加
// valueToAdd 为 ageInfo
// 将 ageInfo 添加到 Set 中
proxy.add(ageInfo)
// 第二次添加
// valueToAdd 为 ageInfo
// 满足 target.__proto__.has(ageInfo),不添加
proxy.add(ageInfo)

示例二

1
2
3
4
5
6
7
8
9
10
11
12
const obj = { value: 18 }
const ageInfo = reactive(obj)
const proxy = reactive(new Set())

// 第一次添加
// valueToAdd 为 ageInfo 的原始值 obj
// 将 obj 添加到 Set 中
proxy.add(ageInfo)
// 第二次添加
// valueToAdd 为 ageInfo 的原始值 obj
// 满足 target.__proto__.has(obj),不添加
proxy.add(ageInfo)
  • 情况2: hasChanged(value, valueToAdd) && proto.has.call(target, value)

满足情况2基本都满足情况1,是否存在情况2的情况?

  • 情况3: hasChanged(rawValue, valueToAdd) && proto.has.call(target, rawValue)

示例1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const obj = { value: 18 }
const ageInfo = reactive(obj)
const ageInfoReadOnly = readonly(ageInfo)
const proxy = reactive(new Set())

// 第一次添加
// valueToAdd 为 ageInfo 的原始值 obj
// 将 obj 添加到 Set 中
proxy.add(ageInfo)
// 第二次添加
// valueToAdd 为 ageInfoReadOnly
// rowValue 为 obj,满足 !Object.is(rawValue, valueToAdd)
// 且满足 target.__proto__.has(obj),不添加
proxy.add(ageInfoReadOnly)

示例二

1
2
3
4
5
6
7
8
9
10
const obj = { value: 18 }
const ageInfo = reactive(obj)
const ageInfoShallowReactive = shallowReactive(obj)
const proxy = reactive(new Set())

proxy.add(ageInfo)
// valueToAdd 为 ageInfoShallowReactive
// rowValue 为 obj,满足 !Object.is(rawValue, valueToAdd)
// 且满足 target.__proto__.has(obj),不添加
proxy.add(ageInfoShallowReactive)

!readonly 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
// packages/reactive/src/collectionHandlers.ts
set(this: MapTypes, key: unknown, value: unknown) {
if (!shallow && !isShallow(value) && !isReadonly(value)) {
value = toRaw(value)
}
const target = toRaw(this)
const { has, get } = getProto(target)

let hadKey = has.call(target, key)
if (!hadKey) {
key = toRaw(key)
hadKey = has.call(target, key)
} else if (__DEV__) {
checkIdentityKeys(target, has, key)
}

const oldValue = get.call(target, key)
target.set(key, value)
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
return this
}

转换value

1
2
3
4
// packages/reactive/src/collectionHandlers.ts
if (!shallow && !isShallow(value) && !isReadonly(value)) {
value = toRaw(value)
}

判断 Handler 是否浅层数据结构模式

  • 如果是浅层数据结构模式,不对 value 做处理
  • 如果不是浅层数据结构模式,再根据其它条件,对 value 做处理

value 可能是原始值或代理值。判断 value 是否是浅层数据结构模式和是否是只读模式。

  • 如果 value 不是由 shallowRef、shallowReactive()、shallowReadonly()、 readonly() 或 shallowReadonly() 创建的代理值, value 值为通过 toRaw 转换的原始值。
  • 否则 value 值为对应的代理值。


设置value

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
const getProto = <T extends CollectionTypes>(v: T): any =>
Reflect.getPrototypeOf(v)

// `this` 指向代理对象,通过 `toRaw` 方法获取代理对象的原始值。
const target = toRaw(this)
// 通过 `getProto` 从原型对象中获取 `has` 和 `get` 方法。
const { has, get } = getProto(target)

// 检查target中是否存在 key 键,key 键可能是原始值或代理值
let hadKey = has.call(target, key)
if (!hadKey) {
// 如果不存在 `key` 键,将 `key` 尝试转换为原始值后再检查
key = toRaw(key)
// 检查target中是否存在 `key` 键
hadKey = has.call(target, key)
} else if (__DEV__) {
// 如果存在 `key` 键,校验 `key` 键
checkIdentityKeys(target, has, key)
}

// 获取 `key` 键的旧值
const oldValue = get.call(target, key)

// 通过 `set` 方法设置 `key` 键的值
target.set(key, value)

当调用代理对象的 set 方法时,键保存的是代理对象原始值,并不会将代理对象当成键。

如果 key 键是代理对象,且多次调用 set 方法,let hadKey = has.call(target, key) 返回的值恒为 false。此时就需要将 key 转换为原始值后再检查。


trigger 响应式更新

1
2
3
4
5
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}

判断 hadKey 的判断当前 set 方法是否是新增键或更新键。

  • 如果是新增键,通过 trigger 方法触发响应式系统更新。
  • 如果是更新键,通过 trigger 方法触发响应式系统更新,并且传递 oldValue 作为参数。

!readonly delete()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
delete(this: CollectionTypes, key: unknown) {
const target = toRaw(this)
const { has, get } = getProto(target)
let hadKey = has.call(target, key)
if (!hadKey) {
key = toRaw(key)
hadKey = has.call(target, key)
} else if (__DEV__) {
checkIdentityKeys(target, has, key)
}

const oldValue = get ? get.call(target, key) : undefined
// forward the operation before queueing reactions
const result = target.delete(key)
if (hadKey) {
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
}
return result
}

Map/Set 的 delete 属性拦截器。如果对象中的元素存在并被移除,则返回 true,如果该元素不存在,则返回 false。

成功删除元素后,通过 trigger 方法触发响应式系统更新。

!readonly clear()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
clear(this: IterableCollections) {
const target = toRaw(this)
const hadItems = target.size !== 0
const oldTarget = __DEV__
? isMap(target)
? new Map(target)
: new Set(target)
: undefined
// forward the operation before queueing reactions
const result = target.clear()
if (hadItems) {
trigger(
target,
TriggerOpTypes.CLEAR,
undefined,
undefined,
oldTarget,
)
}
return result
}

Map/Set 的 clear 属性拦截器。

hadItems 记录对象中是否有元素,如果有元素触发响应式系统更新。否则不需要触发响应式系统更新。

在 DEV 环境下,通过 new Map(target) 或 new Set(target) 创建新的 Map/Set 对象,用于记录旧的 Map/Set 对象。生产环境下为 undefined。

iteratorMethods

1
2
3
4
5
6
7
8
9
10
const iteratorMethods = [
'keys',
'values',
'entries',
Symbol.iterator,
] as const

iteratorMethods.forEach(method => {
instrumentations[method] = createIterableMethod(method, readonly, shallow)
})

生成 Map/Set 的 keys/values/entries/Symbol.iterator 迭代器拦截方法。

createIterableMethod

createIterableMethod 源码

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
function createIterableMethod(
method: string | symbol,
isReadonly: boolean,
isShallow: boolean,
) {
return function (
this: IterableCollections,
...args: unknown[]
): Iterable<unknown> & Iterator<unknown> {
const target = this[ReactiveFlags.RAW]
const rawTarget = toRaw(target)
const targetIsMap = isMap(rawTarget)
const isPair =
method === 'entries' || (method === Symbol.iterator && targetIsMap)
const isKeyOnly = method === 'keys' && targetIsMap
const innerIterator = target[method](...args)
const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
!isReadonly &&
track(
rawTarget,
TrackOpTypes.ITERATE,
isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY,
)
// return a wrapped iterator which returns observed versions of the
// values emitted from the real iterator
return extend(
// inheriting all iterator properties
Object.create(innerIterator),
{
// iterator protocol
next() {
const { value, done } = innerIterator.next()
return done
? { value, done }
: {
value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value),
done,
}
},
},
)
}
}

依赖收集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// packages/shared/src/general.ts
export const isMap = (val: unknown): val is Map<any, any> =>
toTypeString(val) === '[object Map]'

// packages/reactive/src/collectionHandlers.ts

// 获取原始值
// target 可能本身也是代理(如 readonly(reactive(map)))
const target = this[ReactiveFlags.RAW]
// 获取原始 Map/Set 对象
const rawTarget = toRaw(target)
// 判断是否为 Map 对象
const targetIsMap = isMap(rawTarget)
// 判断是否为 Map keys 迭代器
const isKeyOnly = method === 'keys' && targetIsMap

!isReadonly &&
track(
rawTarget,
TrackOpTypes.ITERATE,
isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY,
)

非只读 Map/Set 代理,通过 track 方法收集依赖。如果为 Map keys 迭代器,通过 MAP_KEY_ITERATE_KEY 收集依赖。否则使用 ITERATE_KEY 收集依赖。

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
const map = reactive(new Map([['a', 1]]))

// Effect 1:只关心键
watchEffect(() => {
console.log('Effect 1 触发')
for (const key of map.keys()) {
// 追踪 MAP_KEY_ITERATE_KEY
}
})
// Effect 2:关心值和键
watchEffect(() => {
console.log('Effect 2 触发')
console.log('-----------------')
for (const [key, value] of map.entries()) {
// 追踪 ITERATE_KEY
}
})
setTimeout(() => {
map.set('a', 2) // 修改值
// → Effect 2 触发,Effect 1 不触发(键没变)
}, 1000)
setTimeout(() => {
map.set('b', 3) // 新增键
// → Effect 1 触发(键集合变化)
// → Effect 2 触发(整体迭代变化)
}, 3000)

next 迭代器实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// packages/reactive/src/collectionHandlers.ts
const isPair =
method === 'entries' || (method === Symbol.iterator && targetIsMap)
const innerIterator = target[method](...args)
const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive

return extend(
// inheriting all iterator properties
Object.create(innerIterator),
{
// iterator protocol
next() {
const { value, done } = innerIterator.next()
return done
? { value, done }
: {
value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value),
done,
}
},
},
)

返回一个包装迭代器,返回的迭代器在每次迭代时,会将原始迭代器的返回值包装为响应式值。如果返回的值为键值对(isPair),会将键值对中的键和值都包装为响应式值。否则只包装值。

createReadonlyMethod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// packages/reactive/src/collectionHandlers.ts
function createReadonlyMethod(type: TriggerOpTypes): Function {
return function (this: CollectionTypes, ...args: unknown[]) {
if (__DEV__) {
const key = args[0] ? `on key "${args[0]}" ` : ``
warn(
`${capitalize(type)} operation ${key}failed: target is readonly.`,
toRaw(this),
)
}
return type === TriggerOpTypes.DELETE
? false
: type === TriggerOpTypes.CLEAR
? undefined
: this
}
}

创建只读 Handler 的 add/set/delete/clear 方法,

  • 如果操作类型为 DELETE,返回 false。
  • 如果操作类型为 CLEAR,则返回 undefined。
  • 其他操作类型,返回 this 对象。

checkIdentityKeys()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// packages/reactive/src/collectionHandlers.ts
function checkIdentityKeys(
target: CollectionTypes,
has: (key: unknown) => boolean,
key: unknown,
) {
const rawKey = toRaw(key)
if (rawKey !== key && has.call(target, rawKey)) {
const type = toRawType(target)
warn(
`Reactive ${type} contains both the raw and reactive ` +
`versions of the same object${type === `Map` ? ` as keys` : ``}, ` +
`which can lead to inconsistencies. ` +
`Avoid differentiating between the raw and reactive versions ` +
`of an object and only use the reactive version if possible.`,
)
}
}

key 值可能是原始值或代理值。通过 toRaw 方法获取 key 值的原始值。

如果 rawKey !== key,即 key 是代理对象,且 Map/WeakMap/Set/WeakSet 中同时存在代理对象和代理对象的原始值作为键。触发 Reactive Map/Set包含同一对象的原始版本和反应版本作为键 警告。应避免区分对象的原始版本和响应式版本,并尽可能仅使用响应式版本。

触发示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const obj = { key: 'age' }

const ageKey = reactive(obj)

const proxy = reactive(
new Map([
[obj, 18], // 原始值键
[ageKey, 18], // 响应式值键
]),
)

// 满足 rawKey !== key 且满足 proxy.__proto__.has(rawKey)
proxy.set(ageKey, 20) // 触发警告
// 不满足 rawKey !== key
proxy.set(obj, 20) // 不触发警告


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