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) 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)', () => { test('observed value should proxy mutations to original (Object)', () => {
const original: any = { foo: 1 } const original: any = { foo: 1 }
const observed = reactive(original) 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 { import {
mutableHandlers, mutableHandlers,
readonlyHandlers, readonlyHandlers,
@ -30,17 +30,31 @@ export interface Target {
[ReactiveFlags.READONLY]?: any [ReactiveFlags.READONLY]?: any
} }
const collectionTypes = new Set<Function>([Set, Map, WeakMap, WeakSet]) const enum TargetType {
const isObservableType = /*#__PURE__*/ makeMap( INVALID = 0,
'Object,Array,Map,Set,WeakMap,WeakSet' COMMON = 1,
) COLLECTION = 2
}
const canObserve = (value: Target): boolean => { function targetTypeMap(rawType: string) {
return ( switch (rawType) {
!value[ReactiveFlags.SKIP] && case 'Object':
isObservableType(toRawType(value)) && case 'Array':
Object.isExtensible(value) 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 // only unwrap nested ref
@ -148,12 +162,13 @@ function createReactiveObject(
return target[reactiveFlag] return target[reactiveFlag]
} }
// only a whitelist of value types can be observed. // only a whitelist of value types can be observed.
if (!canObserve(target)) { const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target return target
} }
const observed = new Proxy( const observed = new Proxy(
target, target,
collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
) )
def(target, reactiveFlag, observed) def(target, reactiveFlag, observed)
return observed return observed