Skip to content

Commit d005b57

Browse files
authored
fix(reactivity): accept subtypes of collections (#1864)
1 parent 6ccd9ac commit d005b57

File tree

2 files changed

+46
-13
lines changed

2 files changed

+46
-13
lines changed

packages/reactivity/__tests__/reactive.spec.ts

+18
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,24 @@ describe('reactivity/reactive', () => {
4444
expect(isReactive(observed.array[0])).toBe(true)
4545
})
4646

47+
test('process subtypes of collections properly', () => {
48+
class CustomMap extends Map {
49+
count = 0
50+
51+
set(key: any, value: any): this {
52+
super.set(key, value)
53+
this.count++
54+
return this
55+
}
56+
}
57+
58+
const testMap = new CustomMap()
59+
const observed = reactive(testMap)
60+
expect(observed.count).toBe(0)
61+
observed.set('test', 'value')
62+
expect(observed.count).toBe(1)
63+
})
64+
4765
test('observed value should proxy mutations to original (Object)', () => {
4866
const original: any = { foo: 1 }
4967
const observed = reactive(original)

packages/reactivity/src/reactive.ts

+28-13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isObject, toRawType, def, hasOwn, makeMap } from '@vue/shared'
1+
import { isObject, toRawType, def, hasOwn } from '@vue/shared'
22
import {
33
mutableHandlers,
44
readonlyHandlers,
@@ -30,17 +30,31 @@ export interface Target {
3030
[ReactiveFlags.READONLY]?: any
3131
}
3232

33-
const collectionTypes = new Set<Function>([Set, Map, WeakMap, WeakSet])
34-
const isObservableType = /*#__PURE__*/ makeMap(
35-
'Object,Array,Map,Set,WeakMap,WeakSet'
36-
)
33+
const enum TargetType {
34+
INVALID = 0,
35+
COMMON = 1,
36+
COLLECTION = 2
37+
}
3738

38-
const canObserve = (value: Target): boolean => {
39-
return (
40-
!value[ReactiveFlags.SKIP] &&
41-
isObservableType(toRawType(value)) &&
42-
Object.isExtensible(value)
43-
)
39+
function targetTypeMap(rawType: string) {
40+
switch (rawType) {
41+
case 'Object':
42+
case 'Array':
43+
return TargetType.COMMON
44+
case 'Map':
45+
case 'Set':
46+
case 'WeakMap':
47+
case 'WeakSet':
48+
return TargetType.COLLECTION
49+
default:
50+
return TargetType.INVALID
51+
}
52+
}
53+
54+
function getTargetType(value: Target) {
55+
return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
56+
? TargetType.INVALID
57+
: targetTypeMap(toRawType(value))
4458
}
4559

4660
// only unwrap nested ref
@@ -148,12 +162,13 @@ function createReactiveObject(
148162
return target[reactiveFlag]
149163
}
150164
// only a whitelist of value types can be observed.
151-
if (!canObserve(target)) {
165+
const targetType = getTargetType(target)
166+
if (targetType === TargetType.INVALID) {
152167
return target
153168
}
154169
const observed = new Proxy(
155170
target,
156-
collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers
171+
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
157172
)
158173
def(target, reactiveFlag, observed)
159174
return observed

0 commit comments

Comments
 (0)