fix(reactivity): accept subtypes of collections (#1864)

This commit is contained in:
ᴜɴвʏтᴇ 2020-08-18 00:17:46 +08:00 committed by GitHub
parent 6ccd9ac2bc
commit d005b578b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 46 additions and 13 deletions

View File

@ -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)

View File

@ -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<Function>([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