Skip to content

Commit 50adc01

Browse files
committed
refactor(reactivity): readonly collections should not track
1 parent ed43810 commit 50adc01

File tree

2 files changed

+63
-23
lines changed

2 files changed

+63
-23
lines changed

packages/reactivity/__tests__/readonly.spec.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,42 @@ describe('reactivity/readonly', () => {
354354
expect(dummy).toBe(2)
355355
})
356356

357+
test('readonly collection should not track', () => {
358+
const map = new Map()
359+
map.set('foo', 1)
360+
361+
const reMap = reactive(map)
362+
const roMap = readonly(map)
363+
364+
let dummy
365+
effect(() => {
366+
dummy = roMap.get('foo')
367+
})
368+
expect(dummy).toBe(1)
369+
reMap.set('foo', 2)
370+
expect(roMap.get('foo')).toBe(2)
371+
// should not trigger
372+
expect(dummy).toBe(1)
373+
})
374+
375+
test('readonly should track and trigger if wrapping reactive original (collection)', () => {
376+
const a = reactive(new Map())
377+
const b = readonly(a)
378+
// should return true since it's wrapping a reactive source
379+
expect(isReactive(b)).toBe(true)
380+
381+
a.set('foo', 1)
382+
383+
let dummy
384+
effect(() => {
385+
dummy = b.get('foo')
386+
})
387+
expect(dummy).toBe(1)
388+
a.set('foo', 2)
389+
expect(b.get('foo')).toBe(2)
390+
expect(dummy).toBe(2)
391+
})
392+
357393
test('wrapping already wrapped value should return same Proxy', () => {
358394
const original = { foo: 1 }
359395
const wrapped = readonly(original)

packages/reactivity/src/collectionHandlers.ts

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -30,40 +30,42 @@ const getProto = <T extends CollectionTypes>(v: T): any =>
3030
function get(
3131
target: MapTypes,
3232
key: unknown,
33-
wrap: typeof toReactive | typeof toReadonly | typeof toShallow
33+
isReadonly = false,
34+
isShallow = false
3435
) {
3536
// #1772: readonly(reactive(Map)) should return readonly + reactive version
3637
// of the value
3738
target = (target as any)[ReactiveFlags.RAW]
3839
const rawTarget = toRaw(target)
3940
const rawKey = toRaw(key)
4041
if (key !== rawKey) {
41-
track(rawTarget, TrackOpTypes.GET, key)
42+
!isReadonly && track(rawTarget, TrackOpTypes.GET, key)
4243
}
43-
track(rawTarget, TrackOpTypes.GET, rawKey)
44+
!isReadonly && track(rawTarget, TrackOpTypes.GET, rawKey)
4445
const { has } = getProto(rawTarget)
46+
const wrap = isReadonly ? toReadonly : isShallow ? toShallow : toReactive
4547
if (has.call(rawTarget, key)) {
4648
return wrap(target.get(key))
4749
} else if (has.call(rawTarget, rawKey)) {
4850
return wrap(target.get(rawKey))
4951
}
5052
}
5153

52-
function has(this: CollectionTypes, key: unknown): boolean {
53-
const target = toRaw(this)
54+
function has(this: CollectionTypes, key: unknown, isReadonly = false): boolean {
55+
const target = (this as any)[ReactiveFlags.RAW]
56+
const rawTarget = toRaw(target)
5457
const rawKey = toRaw(key)
5558
if (key !== rawKey) {
56-
track(target, TrackOpTypes.HAS, key)
59+
!isReadonly && track(rawTarget, TrackOpTypes.HAS, key)
5760
}
58-
track(target, TrackOpTypes.HAS, rawKey)
59-
const has = getProto(target).has
60-
return has.call(target, key) || has.call(target, rawKey)
61+
!isReadonly && track(rawTarget, TrackOpTypes.HAS, rawKey)
62+
return target.has(key) || target.has(rawKey)
6163
}
6264

63-
function size(target: IterableCollections) {
64-
target = toRaw(target)
65-
track(target, TrackOpTypes.ITERATE, ITERATE_KEY)
66-
return Reflect.get(getProto(target), 'size', target)
65+
function size(target: IterableCollections, isReadonly = false) {
66+
target = (target as any)[ReactiveFlags.RAW]
67+
!isReadonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
68+
return Reflect.get(target, 'size', target)
6769
}
6870

6971
function add(this: SetTypes, value: unknown) {
@@ -137,15 +139,15 @@ function clear(this: IterableCollections) {
137139
return result
138140
}
139141

140-
function createForEach(isReadonly: boolean, shallow: boolean) {
142+
function createForEach(isReadonly: boolean, isShallow: boolean) {
141143
return function forEach(
142144
this: IterableCollections,
143145
callback: Function,
144146
thisArg?: unknown
145147
) {
146148
const observed = this
147149
const target = toRaw(observed)
148-
const wrap = isReadonly ? toReadonly : shallow ? toShallow : toReactive
150+
const wrap = isReadonly ? toReadonly : isShallow ? toShallow : toReactive
149151
!isReadonly && track(target, TrackOpTypes.ITERATE, ITERATE_KEY)
150152
// important: create sure the callback is
151153
// 1. invoked with the reactive map as `this` and 3rd arg
@@ -173,19 +175,19 @@ interface IterationResult {
173175
function createIterableMethod(
174176
method: string | symbol,
175177
isReadonly: boolean,
176-
shallow: boolean
178+
isShallow: boolean
177179
) {
178180
return function(
179181
this: IterableCollections,
180182
...args: unknown[]
181183
): Iterable & Iterator {
182184
const target = (this as any)[ReactiveFlags.RAW]
183-
const rawTarget = toRaw(this)
185+
const rawTarget = toRaw(target)
184186
const isMap = rawTarget instanceof Map
185187
const isPair = method === 'entries' || (method === Symbol.iterator && isMap)
186188
const isKeyOnly = method === 'keys' && isMap
187189
const innerIterator = target[method](...args)
188-
const wrap = isReadonly ? toReadonly : shallow ? toShallow : toReactive
190+
const wrap = isReadonly ? toReadonly : isShallow ? toShallow : toReactive
189191
!isReadonly &&
190192
track(
191193
rawTarget,
@@ -228,7 +230,7 @@ function createReadonlyMethod(type: TriggerOpTypes): Function {
228230

229231
const mutableInstrumentations: Record<string, Function> = {
230232
get(this: MapTypes, key: unknown) {
231-
return get(this, key, toReactive)
233+
return get(this, key)
232234
},
233235
get size() {
234236
return size((this as unknown) as IterableCollections)
@@ -243,7 +245,7 @@ const mutableInstrumentations: Record<string, Function> = {
243245

244246
const shallowInstrumentations: Record<string, Function> = {
245247
get(this: MapTypes, key: unknown) {
246-
return get(this, key, toShallow)
248+
return get(this, key, false, true)
247249
},
248250
get size() {
249251
return size((this as unknown) as IterableCollections)
@@ -258,12 +260,14 @@ const shallowInstrumentations: Record<string, Function> = {
258260

259261
const readonlyInstrumentations: Record<string, Function> = {
260262
get(this: MapTypes, key: unknown) {
261-
return get(this, key, toReadonly)
263+
return get(this, key, true)
262264
},
263265
get size() {
264-
return size((this as unknown) as IterableCollections)
266+
return size((this as unknown) as IterableCollections, true)
267+
},
268+
has(this: MapTypes, key: unknown) {
269+
return has.call(this, key, true)
265270
},
266-
has,
267271
add: createReadonlyMethod(TriggerOpTypes.ADD),
268272
set: createReadonlyMethod(TriggerOpTypes.SET),
269273
delete: createReadonlyMethod(TriggerOpTypes.DELETE),

0 commit comments

Comments
 (0)