fix(reactivity): shallowReactive for collections (#1204)

close #1202
This commit is contained in:
Carlos Rodrigues 2020-05-18 16:17:37 +01:00 committed by GitHub
parent ba62ccd55d
commit 488e2bcfef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 77 additions and 15 deletions

View File

@ -187,5 +187,32 @@ describe('reactivity/reactive', () => {
props.n = reactive({ foo: 2 })
expect(isReactive(props.n)).toBe(true)
})
test('should not observe when iterating', () => {
const shallowSet = shallowReactive(new Set())
const a = {}
shallowSet.add(a)
const spreadA = [...shallowSet][0]
expect(isReactive(spreadA)).toBe(false)
})
test('should not get reactive entry', () => {
const shallowMap = shallowReactive(new Map())
const a = {}
const key = 'a'
shallowMap.set(key, a)
expect(isReactive(shallowMap.get(key))).toBe(false)
})
test('should not get reactive on foreach', () => {
const shallowSet = shallowReactive(new Set())
const a = {}
shallowSet.add(a)
shallowSet.forEach(x => expect(isReactive(x)).toBe(false))
})
})
})

View File

@ -22,13 +22,15 @@ const toReactive = <T extends unknown>(value: T): T =>
const toReadonly = <T extends unknown>(value: T): T =>
isObject(value) ? readonly(value) : value
const toShallow = <T extends unknown>(value: T): T => value
const getProto = <T extends CollectionTypes>(v: T): any =>
Reflect.getPrototypeOf(v)
function get(
target: MapTypes,
key: unknown,
wrap: typeof toReactive | typeof toReadonly
wrap: typeof toReactive | typeof toReadonly | typeof toShallow
) {
target = toRaw(target)
const rawKey = toRaw(key)
@ -132,7 +134,7 @@ function clear(this: IterableCollections) {
return result
}
function createForEach(isReadonly: boolean) {
function createForEach(isReadonly: boolean, shallow: boolean) {
return function forEach(
this: IterableCollections,
callback: Function,
@ -140,7 +142,7 @@ function createForEach(isReadonly: boolean) {
) {
const observed = this
const target = toRaw(observed)
const wrap = isReadonly ? toReadonly : toReactive
const wrap = isReadonly ? toReadonly : shallow ? toShallow : toReactive
!isReadonly && track(target, TrackOpTypes.ITERATE, ITERATE_KEY)
// important: create sure the callback is
// 1. invoked with the reactive map as `this` and 3rd arg
@ -152,14 +154,18 @@ function createForEach(isReadonly: boolean) {
}
}
function createIterableMethod(method: string | symbol, isReadonly: boolean) {
function createIterableMethod(
method: string | symbol,
isReadonly: boolean,
shallow: boolean
) {
return function(this: IterableCollections, ...args: unknown[]) {
const target = toRaw(this)
const isMap = target instanceof Map
const isPair = method === 'entries' || (method === Symbol.iterator && isMap)
const isKeyOnly = method === 'keys' && isMap
const innerIterator = getProto(target)[method].apply(target, args)
const wrap = isReadonly ? toReadonly : toReactive
const wrap = isReadonly ? toReadonly : shallow ? toShallow : toReactive
!isReadonly &&
track(
target,
@ -212,7 +218,22 @@ const mutableInstrumentations: Record<string, Function> = {
set,
delete: deleteEntry,
clear,
forEach: createForEach(false)
forEach: createForEach(false, false)
}
const shallowInstrumentations: Record<string, Function> = {
get(this: MapTypes, key: unknown) {
return get(this, key, toShallow)
},
get size() {
return size((this as unknown) as IterableCollections)
},
has,
add,
set,
delete: deleteEntry,
clear,
forEach: createForEach(false, true)
}
const readonlyInstrumentations: Record<string, Function> = {
@ -227,25 +248,34 @@ const readonlyInstrumentations: Record<string, Function> = {
set: createReadonlyMethod(TriggerOpTypes.SET),
delete: createReadonlyMethod(TriggerOpTypes.DELETE),
clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
forEach: createForEach(true)
forEach: createForEach(true, false)
}
const iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator]
iteratorMethods.forEach(method => {
mutableInstrumentations[method as string] = createIterableMethod(
method,
false,
false
)
readonlyInstrumentations[method as string] = createIterableMethod(
method,
true,
false
)
shallowInstrumentations[method as string] = createIterableMethod(
method,
true,
true
)
})
function createInstrumentationGetter(isReadonly: boolean) {
const instrumentations = isReadonly
? readonlyInstrumentations
: mutableInstrumentations
function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
const instrumentations = shallow
? shallowInstrumentations
: isReadonly
? readonlyInstrumentations
: mutableInstrumentations
return (
target: CollectionTypes,
@ -271,11 +301,15 @@ function createInstrumentationGetter(isReadonly: boolean) {
}
export const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {
get: createInstrumentationGetter(false)
get: createInstrumentationGetter(false, false)
}
export const shallowCollectionHandlers: ProxyHandler<CollectionTypes> = {
get: createInstrumentationGetter(false, true)
}
export const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {
get: createInstrumentationGetter(true)
get: createInstrumentationGetter(true, false)
}
function checkIdentityKeys(

View File

@ -7,7 +7,8 @@ import {
} from './baseHandlers'
import {
mutableCollectionHandlers,
readonlyCollectionHandlers
readonlyCollectionHandlers,
shallowCollectionHandlers
} from './collectionHandlers'
import { UnwrapRef, Ref } from './ref'
@ -67,7 +68,7 @@ export function shallowReactive<T extends object>(target: T): T {
target,
false,
shallowReactiveHandlers,
mutableCollectionHandlers
shallowCollectionHandlers
)
}