Skip to content

Commit cc69fd7

Browse files
committed
fix(reactivity): Map/Set identity methods should work even if raw value contains reactive entries
fix #799
1 parent 16f9e63 commit cc69fd7

File tree

5 files changed

+63
-13
lines changed

5 files changed

+63
-13
lines changed

packages/reactivity/__tests__/collections/Map.spec.ts

+14
Original file line numberDiff line numberDiff line change
@@ -348,5 +348,19 @@ describe('reactivity/collections', () => {
348348
map.set('foo', NaN)
349349
expect(mapSpy).toHaveBeenCalledTimes(1)
350350
})
351+
352+
it('should work with reactive keys in raw map', () => {
353+
const raw = new Map()
354+
const key = reactive({})
355+
raw.set(key, 1)
356+
const map = reactive(raw)
357+
358+
expect(map.has(key)).toBe(true)
359+
expect(map.get(key)).toBe(1)
360+
361+
expect(map.delete(key)).toBe(true)
362+
expect(map.has(key)).toBe(false)
363+
expect(map.get(key)).toBeUndefined()
364+
})
351365
})
352366
})

packages/reactivity/__tests__/collections/Set.spec.ts

+12
Original file line numberDiff line numberDiff line change
@@ -368,5 +368,17 @@ describe('reactivity/collections', () => {
368368
})
369369
expect(dummy).toBe(2)
370370
})
371+
372+
it('should work with reactive entries in raw set', () => {
373+
const raw = new Set()
374+
const entry = reactive({})
375+
raw.add(entry)
376+
const set = reactive(raw)
377+
378+
expect(set.has(entry)).toBe(true)
379+
380+
expect(set.delete(entry)).toBe(true)
381+
expect(set.has(entry)).toBe(false)
382+
})
371383
})
372384
})

packages/reactivity/__tests__/reactiveArray.spec.ts

+8
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@ describe('reactivity/reactive/Array', () => {
6767
expect(arr.lastIndexOf(observed, 1)).toBe(-1)
6868
})
6969

70+
test('Array identity methods should work if raw value contains reactive objects', () => {
71+
const raw = []
72+
const obj = reactive({})
73+
raw.push(obj)
74+
const arr = reactive(raw)
75+
expect(arr.includes(obj)).toBe(true)
76+
})
77+
7078
test('Array identity methods should be reactive', () => {
7179
const obj = {}
7280
const arr = reactive([obj, {}])

packages/reactivity/src/baseHandlers.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,14 @@ const arrayInstrumentations: Record<string, Function> = {}
2323
for (let i = 0, l = (this as any).length; i < l; i++) {
2424
track(arr, TrackOpTypes.GET, i + '')
2525
}
26-
return arr[key](...args.map(toRaw))
26+
// we run the method using the orignal args first (which may be reactive)
27+
const res = arr[key](...args)
28+
if (res === -1 || res === false) {
29+
// if that didn't work, run it again using raw values.
30+
return arr[key](...args.map(toRaw))
31+
} else {
32+
return res
33+
}
2734
}
2835
})
2936

packages/reactivity/src/collectionHandlers.ts

+21-12
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,22 @@ function get(
2626
wrap: typeof toReactive | typeof toReadonly
2727
) {
2828
target = toRaw(target)
29-
key = toRaw(key)
30-
track(target, TrackOpTypes.GET, key)
31-
return wrap(getProto(target).get.call(target, key))
29+
const rawKey = toRaw(key)
30+
track(target, TrackOpTypes.GET, rawKey)
31+
const { has, get } = getProto(target)
32+
if (has.call(target, key)) {
33+
return wrap(get.call(target, key))
34+
} else if (has.call(target, rawKey)) {
35+
return wrap(get.call(target, rawKey))
36+
}
3237
}
3338

3439
function has(this: CollectionTypes, key: unknown): boolean {
3540
const target = toRaw(this)
36-
key = toRaw(key)
37-
track(target, TrackOpTypes.HAS, key)
38-
return getProto(target).has.call(target, key)
41+
const rawKey = toRaw(key)
42+
track(target, TrackOpTypes.HAS, rawKey)
43+
const has = getProto(target).has
44+
return has.call(target, key) || has.call(target, rawKey)
3945
}
4046

4147
function size(target: IterableCollections) {
@@ -73,13 +79,16 @@ function set(this: MapTypes, key: unknown, value: unknown) {
7379
}
7480

7581
function deleteEntry(this: CollectionTypes, key: unknown) {
76-
key = toRaw(key)
7782
const target = toRaw(this)
78-
const proto = getProto(target)
79-
const hadKey = proto.has.call(target, key)
80-
const oldValue = proto.get ? proto.get.call(target, key) : undefined
83+
const { has, get, delete: del } = getProto(target)
84+
let hadKey = has.call(target, key)
85+
if (!hadKey) {
86+
key = toRaw(key)
87+
hadKey = has.call(target, key)
88+
}
89+
const oldValue = get ? get.call(target, key) : undefined
8190
// forward the operation before queueing reactions
82-
const result = proto.delete.call(target, key)
91+
const result = del.call(target, key)
8392
if (hadKey) {
8493
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
8594
}
@@ -177,7 +186,7 @@ const mutableInstrumentations: Record<string, Function> = {
177186
return get(this, key, toReactive)
178187
},
179188
get size() {
180-
return size(this as unknown as IterableCollections)
189+
return size((this as unknown) as IterableCollections)
181190
},
182191
has,
183192
add,

0 commit comments

Comments
 (0)