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

View File

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