本文解析 Vue3
reactivity模块中 Collection(Map/Set/WeakMap/WeakSet) 的响应式处理逻辑,重点在设计思路与源码结构,而不是 API 使用。
1. 背景:为什么 Map/Set 要单独写代码?
Vue 对普通对象 {} 的响应式靠 get/set 即可,但对 Map/Set:
- 方法是函数 (如
.set()、.get()) - 键可能是对象
- 有迭代器 (
keys、values、entries、for...of) - 弱集合 WeakMap/WeakSet 无法遍历
因此,Vue 必须提供一套专门的"仪表方法"(instrumentations)来接管所有操作。
这一段源码就是专门为此而设计的。
2. createIterableMethod ------ 迭代器逻辑封装
代码片段
javascript
function createIterableMethod(method, isReadonly, isShallow) {
return function (...args) {
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 {
next() {
const { value, done } = innerIterator.next()
return done
? { value, done }
: { value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value), done }
},
[Symbol.iterator]() {
return this
},
}
}
}
逐段解释:
① 获取原始对象
ini
const target = this[ReactiveFlags.RAW]
const rawTarget = toRaw(target)
Map 可能是嵌套 reactive 包装,这里确保拿到真正的原对象。
② 判断返回值是 key/value/key+value
ini
const isPair = method === 'entries' ...
const isKeyOnly = method === 'keys'
③ 执行原生迭代器
ini
const innerIterator = target[method](...args)
④ 依赖收集
scss
track(rawTarget, TrackOpTypes.ITERATE, ITERATE_KEY)
关键点:只要调用 keys、values、entries、for-of,都要追踪依赖。
这样当集合结构变化时才能触发更新。
⑤ 迭代器包装:将每个值转成 reactive
css
value: wrap(...)
Map 中存的值可能是对象,Vue 自动转换成 reactive 或 readonly。
这一部分是最核心的"响应式迭代器"实现。
3. createReadonlyMethod ------ 用于 readonly 集合
代码片段
typescript
function createReadonlyMethod(type) {
return function (...args) {
warn(`${capitalize(type)} operation failed: target is readonly.`)
return type === TriggerOpTypes.DELETE
? false
: type === TriggerOpTypes.CLEAR ? undefined : this
}
}
思路:
- 任何修改(set/add/delete/clear)都会提示错误
- 并返回合理的 fallback 值
举例:
delete()→falseclear()→undefinedset/add→ 返回 this(保持链式调用)
4. createInstrumentations ------ 生成 Map/Set 全量代理方法
它会根据参数 readonly 和 shallow 返回不同版本的方法表。
(1)get(key)
代码片段
vbnet
get(key) {
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))
}
}
关键细节:
① key 和 rawKey 都要 track
原因:用户可能用 reactive(obj) 或 raw obj 作为 map key。
② 始终 wrap 返回值
所有 get 到的 value 必须返回 reactive/readonly 版本。
(2)size 属性
csharp
get size() {
const target = this[ReactiveFlags.RAW]
!readonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
return target.size
}
size 依赖整个集合结构,所以也要使用 ITERATE 依赖类型。
(3)has(key)
逻辑和 get 类似,也要对 key 与 rawKey 都进行 track。
(4)forEach
Vue 为回调参数做响应式包装:
scss
callback.call(thisArg, wrap(value), wrap(key), observed)
确保:
- value 是 reactive
- key 是 reactive
- this 是 reactive
5. Mutation(增删改清)操作
如果不是 readonly,则提供真正的 set/add/delete/clear。
(1)add (for Set)
scss
add(value) {
value = toRaw(value)
const hadKey = target.has(value)
if (!hadKey) {
target.add(value)
trigger(target, TriggerOpTypes.ADD, value, value)
}
return this
}
(2)set (for Map)
核心逻辑
- 把 value 转 raw
- 判断 key 是否存在
- 触发 ADD 或 SET
(3)delete
触发 DELETE 类型依赖,并将旧值传递给 watcher。
(4)clear
触发 CLEAR,且如果是 Map 会克隆 oldTarget(仅 dev)。
6. iterator 方法统一挂载
sql
['keys','values','entries',Symbol.iterator].forEach(method => {
instrumentations[method] = createIterableMethod(method, readonly, shallow)
})
这是响应式迭代器的核心。
7. createInstrumentationGetter ------ Proxy.get 捕获器
它定义了所有 Collection 的代理行为。
vbnet
get(target, key, receiver) {
if (key === ReactiveFlags.IS_REACTIVE) return !isReadonly
if (key === ReactiveFlags.IS_READONLY) return isReadonly
if (key === ReactiveFlags.RAW) return target
return Reflect.get(
hasOwn(instrumentations, key) && key in target
? instrumentations
: target,
key,
receiver,
)
}
逻辑:
- 读标识位(isReactive/isReadonly/raw)
- 如果有对应的 instrumentations 方法,则从那里取
- 否则取集合原本的方法
8. 最终导出的四种 handler
arduino
export const mutableCollectionHandlers
export const shallowCollectionHandlers
export const readonlyCollectionHandlers
export const shallowReadonlyCollectionHandlers
Vue 会根据:
- reactive()
- shallowReactive()
- readonly()
- shallowReadonly()
来使用不同的 handler。
总结
这段源码是 Vue3 响应式系统中最复杂的一部分之一,核心设计理念包括:
- 以 instrumentations 表包装所有 Collection 方法
- 迭代器统一代理,确保 for-of / keys / values 都能被追踪
- 返回值自动 wrap 成 reactive 或 readonly
- 同时支持 raw key 与 reactive key 的一致性检查
- 对不同模式(shallow / readonly)进行统一抽象
它是 Vue 能优雅支持 Map/Set 响应式的关键。
本文部分内容借助 AI 辅助生成,并由作者整理审核。