Skip to content
This repository was archived by the owner on Jul 19, 2025. It is now read-only.

Commit c82b662

Browse files
authoredOct 11, 2024
refactor(reactivity): reduce size of collectionHandlers (#12152)
1 parent ea943af commit c82b662

File tree

1 file changed

+154
-234
lines changed

1 file changed

+154
-234
lines changed
 

‎packages/reactivity/src/collectionHandlers.ts

Lines changed: 154 additions & 234 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,14 @@ import {
88
} from './reactive'
99
import { ITERATE_KEY, MAP_KEY_ITERATE_KEY, track, trigger } from './dep'
1010
import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants'
11-
import { capitalize, hasChanged, hasOwn, isMap, toRawType } from '@vue/shared'
11+
import {
12+
capitalize,
13+
extend,
14+
hasChanged,
15+
hasOwn,
16+
isMap,
17+
toRawType,
18+
} from '@vue/shared'
1219
import { warn } from './warning'
1320

1421
type CollectionTypes = IterableCollections | WeakCollections
@@ -23,152 +30,6 @@ const toShallow = <T extends unknown>(value: T): T => value
2330
const getProto = <T extends CollectionTypes>(v: T): any =>
2431
Reflect.getPrototypeOf(v)
2532

26-
function get(
27-
target: MapTypes,
28-
key: unknown,
29-
isReadonly = false,
30-
isShallow = false,
31-
) {
32-
// #1772: readonly(reactive(Map)) should return readonly + reactive version
33-
// of the value
34-
target = target[ReactiveFlags.RAW]
35-
const rawTarget = toRaw(target)
36-
const rawKey = toRaw(key)
37-
if (!isReadonly) {
38-
if (hasChanged(key, rawKey)) {
39-
track(rawTarget, TrackOpTypes.GET, key)
40-
}
41-
track(rawTarget, TrackOpTypes.GET, rawKey)
42-
}
43-
const { has } = getProto(rawTarget)
44-
const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
45-
if (has.call(rawTarget, key)) {
46-
return wrap(target.get(key))
47-
} else if (has.call(rawTarget, rawKey)) {
48-
return wrap(target.get(rawKey))
49-
} else if (target !== rawTarget) {
50-
// #3602 readonly(reactive(Map))
51-
// ensure that the nested reactive `Map` can do tracking for itself
52-
target.get(key)
53-
}
54-
}
55-
56-
function has(this: CollectionTypes, key: unknown, isReadonly = false): boolean {
57-
const target = this[ReactiveFlags.RAW]
58-
const rawTarget = toRaw(target)
59-
const rawKey = toRaw(key)
60-
if (!isReadonly) {
61-
if (hasChanged(key, rawKey)) {
62-
track(rawTarget, TrackOpTypes.HAS, key)
63-
}
64-
track(rawTarget, TrackOpTypes.HAS, rawKey)
65-
}
66-
return key === rawKey
67-
? target.has(key)
68-
: target.has(key) || target.has(rawKey)
69-
}
70-
71-
function size(target: IterableCollections, isReadonly = false) {
72-
target = target[ReactiveFlags.RAW]
73-
!isReadonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
74-
return Reflect.get(target, 'size', target)
75-
}
76-
77-
function add(this: SetTypes, value: unknown, _isShallow = false) {
78-
if (!_isShallow && !isShallow(value) && !isReadonly(value)) {
79-
value = toRaw(value)
80-
}
81-
const target = toRaw(this)
82-
const proto = getProto(target)
83-
const hadKey = proto.has.call(target, value)
84-
if (!hadKey) {
85-
target.add(value)
86-
trigger(target, TriggerOpTypes.ADD, value, value)
87-
}
88-
return this
89-
}
90-
91-
function set(this: MapTypes, key: unknown, value: unknown, _isShallow = false) {
92-
if (!_isShallow && !isShallow(value) && !isReadonly(value)) {
93-
value = toRaw(value)
94-
}
95-
const target = toRaw(this)
96-
const { has, get } = getProto(target)
97-
98-
let hadKey = has.call(target, key)
99-
if (!hadKey) {
100-
key = toRaw(key)
101-
hadKey = has.call(target, key)
102-
} else if (__DEV__) {
103-
checkIdentityKeys(target, has, key)
104-
}
105-
106-
const oldValue = get.call(target, key)
107-
target.set(key, value)
108-
if (!hadKey) {
109-
trigger(target, TriggerOpTypes.ADD, key, value)
110-
} else if (hasChanged(value, oldValue)) {
111-
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
112-
}
113-
return this
114-
}
115-
116-
function deleteEntry(this: CollectionTypes, key: unknown) {
117-
const target = toRaw(this)
118-
const { has, get } = getProto(target)
119-
let hadKey = has.call(target, key)
120-
if (!hadKey) {
121-
key = toRaw(key)
122-
hadKey = has.call(target, key)
123-
} else if (__DEV__) {
124-
checkIdentityKeys(target, has, key)
125-
}
126-
127-
const oldValue = get ? get.call(target, key) : undefined
128-
// forward the operation before queueing reactions
129-
const result = target.delete(key)
130-
if (hadKey) {
131-
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
132-
}
133-
return result
134-
}
135-
136-
function clear(this: IterableCollections) {
137-
const target = toRaw(this)
138-
const hadItems = target.size !== 0
139-
const oldTarget = __DEV__
140-
? isMap(target)
141-
? new Map(target)
142-
: new Set(target)
143-
: undefined
144-
// forward the operation before queueing reactions
145-
const result = target.clear()
146-
if (hadItems) {
147-
trigger(target, TriggerOpTypes.CLEAR, undefined, undefined, oldTarget)
148-
}
149-
return result
150-
}
151-
152-
function createForEach(isReadonly: boolean, isShallow: boolean) {
153-
return function forEach(
154-
this: IterableCollections,
155-
callback: Function,
156-
thisArg?: unknown,
157-
) {
158-
const observed = this
159-
const target = observed[ReactiveFlags.RAW]
160-
const rawTarget = toRaw(target)
161-
const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
162-
!isReadonly && track(rawTarget, TrackOpTypes.ITERATE, ITERATE_KEY)
163-
return target.forEach((value: unknown, key: unknown) => {
164-
// important: make sure the callback is
165-
// 1. invoked with the reactive map as `this` and 3rd arg
166-
// 2. the value received should be a corresponding reactive/readonly.
167-
return callback.call(thisArg, wrap(value), wrap(key), observed)
168-
})
169-
}
170-
}
171-
17233
function createIterableMethod(
17334
method: string | symbol,
17435
isReadonly: boolean,
@@ -232,74 +93,158 @@ function createReadonlyMethod(type: TriggerOpTypes): Function {
23293

23394
type Instrumentations = Record<string | symbol, Function | number>
23495

235-
function createInstrumentations() {
236-
const mutableInstrumentations: Instrumentations = {
237-
get(this: MapTypes, key: unknown) {
238-
return get(this, key)
239-
},
240-
get size() {
241-
return size(this as unknown as IterableCollections)
242-
},
243-
has,
244-
add,
245-
set,
246-
delete: deleteEntry,
247-
clear,
248-
forEach: createForEach(false, false),
249-
}
250-
251-
const shallowInstrumentations: Instrumentations = {
96+
function createInstrumentations(
97+
readonly: boolean,
98+
shallow: boolean,
99+
): Instrumentations {
100+
const instrumentations: Instrumentations = {
252101
get(this: MapTypes, key: unknown) {
253-
return get(this, key, false, true)
102+
// #1772: readonly(reactive(Map)) should return readonly + reactive version
103+
// of the value
104+
const target = this[ReactiveFlags.RAW]
105+
const rawTarget = toRaw(target)
106+
const rawKey = toRaw(key)
107+
if (!readonly) {
108+
if (hasChanged(key, rawKey)) {
109+
track(rawTarget, TrackOpTypes.GET, key)
110+
}
111+
track(rawTarget, TrackOpTypes.GET, rawKey)
112+
}
113+
const { has } = getProto(rawTarget)
114+
const wrap = shallow ? toShallow : readonly ? toReadonly : toReactive
115+
if (has.call(rawTarget, key)) {
116+
return wrap(target.get(key))
117+
} else if (has.call(rawTarget, rawKey)) {
118+
return wrap(target.get(rawKey))
119+
} else if (target !== rawTarget) {
120+
// #3602 readonly(reactive(Map))
121+
// ensure that the nested reactive `Map` can do tracking for itself
122+
target.get(key)
123+
}
254124
},
255125
get size() {
256-
return size(this as unknown as IterableCollections)
126+
const target = (this as unknown as IterableCollections)[ReactiveFlags.RAW]
127+
!readonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
128+
return Reflect.get(target, 'size', target)
257129
},
258-
has,
259-
add(this: SetTypes, value: unknown) {
260-
return add.call(this, value, true)
130+
has(this: CollectionTypes, key: unknown): boolean {
131+
const target = this[ReactiveFlags.RAW]
132+
const rawTarget = toRaw(target)
133+
const rawKey = toRaw(key)
134+
if (!readonly) {
135+
if (hasChanged(key, rawKey)) {
136+
track(rawTarget, TrackOpTypes.HAS, key)
137+
}
138+
track(rawTarget, TrackOpTypes.HAS, rawKey)
139+
}
140+
return key === rawKey
141+
? target.has(key)
142+
: target.has(key) || target.has(rawKey)
261143
},
262-
set(this: MapTypes, key: unknown, value: unknown) {
263-
return set.call(this, key, value, true)
144+
forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {
145+
const observed = this
146+
const target = observed[ReactiveFlags.RAW]
147+
const rawTarget = toRaw(target)
148+
const wrap = shallow ? toShallow : readonly ? toReadonly : toReactive
149+
!readonly && track(rawTarget, TrackOpTypes.ITERATE, ITERATE_KEY)
150+
return target.forEach((value: unknown, key: unknown) => {
151+
// important: make sure the callback is
152+
// 1. invoked with the reactive map as `this` and 3rd arg
153+
// 2. the value received should be a corresponding reactive/readonly.
154+
return callback.call(thisArg, wrap(value), wrap(key), observed)
155+
})
264156
},
265-
delete: deleteEntry,
266-
clear,
267-
forEach: createForEach(false, true),
268157
}
269158

270-
const readonlyInstrumentations: Instrumentations = {
271-
get(this: MapTypes, key: unknown) {
272-
return get(this, key, true)
273-
},
274-
get size() {
275-
return size(this as unknown as IterableCollections, true)
276-
},
277-
has(this: MapTypes, key: unknown) {
278-
return has.call(this, key, true)
279-
},
280-
add: createReadonlyMethod(TriggerOpTypes.ADD),
281-
set: createReadonlyMethod(TriggerOpTypes.SET),
282-
delete: createReadonlyMethod(TriggerOpTypes.DELETE),
283-
clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
284-
forEach: createForEach(true, false),
285-
}
159+
extend(
160+
instrumentations,
161+
readonly
162+
? {
163+
add: createReadonlyMethod(TriggerOpTypes.ADD),
164+
set: createReadonlyMethod(TriggerOpTypes.SET),
165+
delete: createReadonlyMethod(TriggerOpTypes.DELETE),
166+
clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
167+
}
168+
: {
169+
add(this: SetTypes, value: unknown) {
170+
if (!shallow && !isShallow(value) && !isReadonly(value)) {
171+
value = toRaw(value)
172+
}
173+
const target = toRaw(this)
174+
const proto = getProto(target)
175+
const hadKey = proto.has.call(target, value)
176+
if (!hadKey) {
177+
target.add(value)
178+
trigger(target, TriggerOpTypes.ADD, value, value)
179+
}
180+
return this
181+
},
182+
set(this: MapTypes, key: unknown, value: unknown) {
183+
if (!shallow && !isShallow(value) && !isReadonly(value)) {
184+
value = toRaw(value)
185+
}
186+
const target = toRaw(this)
187+
const { has, get } = getProto(target)
188+
189+
let hadKey = has.call(target, key)
190+
if (!hadKey) {
191+
key = toRaw(key)
192+
hadKey = has.call(target, key)
193+
} else if (__DEV__) {
194+
checkIdentityKeys(target, has, key)
195+
}
286196

287-
const shallowReadonlyInstrumentations: Instrumentations = {
288-
get(this: MapTypes, key: unknown) {
289-
return get(this, key, true, true)
290-
},
291-
get size() {
292-
return size(this as unknown as IterableCollections, true)
293-
},
294-
has(this: MapTypes, key: unknown) {
295-
return has.call(this, key, true)
296-
},
297-
add: createReadonlyMethod(TriggerOpTypes.ADD),
298-
set: createReadonlyMethod(TriggerOpTypes.SET),
299-
delete: createReadonlyMethod(TriggerOpTypes.DELETE),
300-
clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
301-
forEach: createForEach(true, true),
302-
}
197+
const oldValue = get.call(target, key)
198+
target.set(key, value)
199+
if (!hadKey) {
200+
trigger(target, TriggerOpTypes.ADD, key, value)
201+
} else if (hasChanged(value, oldValue)) {
202+
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
203+
}
204+
return this
205+
},
206+
delete(this: CollectionTypes, key: unknown) {
207+
const target = toRaw(this)
208+
const { has, get } = getProto(target)
209+
let hadKey = has.call(target, key)
210+
if (!hadKey) {
211+
key = toRaw(key)
212+
hadKey = has.call(target, key)
213+
} else if (__DEV__) {
214+
checkIdentityKeys(target, has, key)
215+
}
216+
217+
const oldValue = get ? get.call(target, key) : undefined
218+
// forward the operation before queueing reactions
219+
const result = target.delete(key)
220+
if (hadKey) {
221+
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
222+
}
223+
return result
224+
},
225+
clear(this: IterableCollections) {
226+
const target = toRaw(this)
227+
const hadItems = target.size !== 0
228+
const oldTarget = __DEV__
229+
? isMap(target)
230+
? new Map(target)
231+
: new Set(target)
232+
: undefined
233+
// forward the operation before queueing reactions
234+
const result = target.clear()
235+
if (hadItems) {
236+
trigger(
237+
target,
238+
TriggerOpTypes.CLEAR,
239+
undefined,
240+
undefined,
241+
oldTarget,
242+
)
243+
}
244+
return result
245+
},
246+
},
247+
)
303248

304249
const iteratorMethods = [
305250
'keys',
@@ -309,39 +254,14 @@ function createInstrumentations() {
309254
] as const
310255

311256
iteratorMethods.forEach(method => {
312-
mutableInstrumentations[method] = createIterableMethod(method, false, false)
313-
readonlyInstrumentations[method] = createIterableMethod(method, true, false)
314-
shallowInstrumentations[method] = createIterableMethod(method, false, true)
315-
shallowReadonlyInstrumentations[method] = createIterableMethod(
316-
method,
317-
true,
318-
true,
319-
)
257+
instrumentations[method] = createIterableMethod(method, readonly, shallow)
320258
})
321259

322-
return [
323-
mutableInstrumentations,
324-
readonlyInstrumentations,
325-
shallowInstrumentations,
326-
shallowReadonlyInstrumentations,
327-
]
260+
return instrumentations
328261
}
329262

330-
const [
331-
mutableInstrumentations,
332-
readonlyInstrumentations,
333-
shallowInstrumentations,
334-
shallowReadonlyInstrumentations,
335-
] = /* @__PURE__*/ createInstrumentations()
336-
337263
function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
338-
const instrumentations = shallow
339-
? isReadonly
340-
? shallowReadonlyInstrumentations
341-
: shallowInstrumentations
342-
: isReadonly
343-
? readonlyInstrumentations
344-
: mutableInstrumentations
264+
const instrumentations = createInstrumentations(isReadonly, shallow)
345265

346266
return (
347267
target: CollectionTypes,

0 commit comments

Comments
 (0)
This repository has been archived.