diff --git a/packages/reactivity/__tests__/reactive.spec.ts b/packages/reactivity/__tests__/reactive.spec.ts index b7f9cdf4eb3..ab798ff2c8b 100644 --- a/packages/reactivity/__tests__/reactive.spec.ts +++ b/packages/reactivity/__tests__/reactive.spec.ts @@ -44,6 +44,24 @@ describe('reactivity/reactive', () => { expect(isReactive(observed.array[0])).toBe(true) }) + test('process subtypes of collections properly', () => { + class CustomMap extends Map { + count = 0 + + set(key: any, value: any): this { + super.set(key, value) + this.count++ + return this + } + } + + const testMap = new CustomMap() + const observed = reactive(testMap) + expect(observed.count).toBe(0) + observed.set('test', 'value') + expect(observed.count).toBe(1) + }) + test('observed value should proxy mutations to original (Object)', () => { const original: any = { foo: 1 } const observed = reactive(original) diff --git a/packages/reactivity/src/reactive.ts b/packages/reactivity/src/reactive.ts index 7541886c405..a6602db2c5c 100644 --- a/packages/reactivity/src/reactive.ts +++ b/packages/reactivity/src/reactive.ts @@ -1,4 +1,4 @@ -import { isObject, toRawType, def, hasOwn, makeMap } from '@vue/shared' +import { isObject, toRawType, def, hasOwn } from '@vue/shared' import { mutableHandlers, readonlyHandlers, @@ -30,17 +30,31 @@ export interface Target { [ReactiveFlags.READONLY]?: any } -const collectionTypes = new Set([Set, Map, WeakMap, WeakSet]) -const isObservableType = /*#__PURE__*/ makeMap( - 'Object,Array,Map,Set,WeakMap,WeakSet' -) +const enum TargetType { + INVALID = 0, + COMMON = 1, + COLLECTION = 2 +} -const canObserve = (value: Target): boolean => { - return ( - !value[ReactiveFlags.SKIP] && - isObservableType(toRawType(value)) && - Object.isExtensible(value) - ) +function targetTypeMap(rawType: string) { + switch (rawType) { + case 'Object': + case 'Array': + return TargetType.COMMON + case 'Map': + case 'Set': + case 'WeakMap': + case 'WeakSet': + return TargetType.COLLECTION + default: + return TargetType.INVALID + } +} + +function getTargetType(value: Target) { + return value[ReactiveFlags.SKIP] || !Object.isExtensible(value) + ? TargetType.INVALID + : targetTypeMap(toRawType(value)) } // only unwrap nested ref @@ -148,12 +162,13 @@ function createReactiveObject( return target[reactiveFlag] } // only a whitelist of value types can be observed. - if (!canObserve(target)) { + const targetType = getTargetType(target) + if (targetType === TargetType.INVALID) { return target } const observed = new Proxy( target, - collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers + targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers ) def(target, reactiveFlag, observed) return observed