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: createInstrumentationGetter (false , false ), }export const shallowCollectionHandlers: ProxyHandler<CollectionTypes> = { get: createInstrumentationGetter (false , true ), }export const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = { get: createInstrumentationGetter (true , false ), }export const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = { get: 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 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), ]) })
处理其它键
1 2 3 4 5 6 7 8 9 10 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) { 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) { 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) => { 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 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 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) { 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) { 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 export function toRaw<T>(observed : T): T { const raw = observed && (observed as Target )[ReactiveFlags .RAW ] return raw ? toRaw (raw) : observed }export const hasChanged = (value : any , oldValue : any ): boolean => !Object .is (value, oldValue)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 const rawTarget = toRaw(target )const rawKey = toRaw(key)const { has } = getProto(rawTarget)const wrap = shallow ? toShallow : readonly ? toReadonly : toReactiveif (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 , 18], [labelProxy, 20], ]), )const age = mapProxy.get (label )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 , 19], ]), )const age = mapProxy.get (labelProxy)
处理嵌套的响应式 Map
1 2 3 4 5 6 else if (target !== rawTarget) { 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 (() => { const roItem = roMap.get (label) console.log ('Watch effect ro item:' , roItem) }) map.set (label , { age: 18 })
get size()
1 2 3 4 5 6 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(() => { const size = map .size console .log ('size' , size) }) setTimeout(() => { map .set('name' , 'xixi' ) }) setTimeout(() => { map .delete('age' ) }) setTimeout(() => { map .clear() })
has()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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 ]]))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 ) => { 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 ) => { const _value = value const _key = key 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 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()) proxy.add (ageInfo) proxy.add (nameInfo) proxy.add (hobbyInfo) proxy.add (sexInfo) proxy.add ({ height: 170 , })
add 执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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()) proxy.add (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()) proxy.add (ageInfo) 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()) proxy.add (ageInfo) 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) 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 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 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 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 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 => )
生成 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 extend ( Object .create (innerIterator), { 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 export const isMap = (val : unknown): val is Map<any, any> => toTypeString(val ) === '[object Map]' const target = this [ReactiveFlags.RAW]const rawTarget = toRaw(target)const targetIsMap = isMap(rawTarget)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] ]))watchEffect (() => { console.log ('Effect 1 触发' ) for (const key of map.keys ()) { } })watchEffect (() => { console.log ('Effect 2 触发' ) console.log ('-----------------' ) for (const [key, value] of map.entries ()) { } })setTimeout (() => { map.set ('a' , 2 ) }, 1000 )setTimeout (() => { map.set ('b' , 3 ) }, 3000 )
next 迭代器实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const isPair = method === 'entries ' || (method === Symbol.iterator && targetIsMap) const innerIterator = target [method ](...args) const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive return extend( Object.create(innerIterator), = innerIterator.next() return done ? : }, }, )
返回一个包装迭代器,返回的迭代器在每次迭代时,会将原始迭代器的返回值包装为响应式值。如果返回的值为键值对(isPair),会将键值对中的键和值都包装为响应式值。否则只包装值。
createReadonlyMethod
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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 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 ], ]), ) proxy.set (ageKey, 20 ) proxy.set (obj, 20 )