diff --git a/packages/reactivity/__tests__/reactive.spec.ts b/packages/reactivity/__tests__/reactive.spec.ts index 44fc5391df1..6d2c68962d4 100644 --- a/packages/reactivity/__tests__/reactive.spec.ts +++ b/packages/reactivity/__tests__/reactive.spec.ts @@ -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)) + }) }) }) diff --git a/packages/reactivity/src/collectionHandlers.ts b/packages/reactivity/src/collectionHandlers.ts index 5aff152e939..181a6e2152c 100644 --- a/packages/reactivity/src/collectionHandlers.ts +++ b/packages/reactivity/src/collectionHandlers.ts @@ -22,13 +22,15 @@ const toReactive = (value: T): T => const toReadonly = (value: T): T => isObject(value) ? readonly(value) : value +const toShallow = (value: T): T => value + const getProto = (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 = { set, delete: deleteEntry, clear, - forEach: createForEach(false) + forEach: createForEach(false, false) +} + +const shallowInstrumentations: Record = { + 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 = { @@ -227,25 +248,34 @@ const readonlyInstrumentations: Record = { 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 = { - get: createInstrumentationGetter(false) + get: createInstrumentationGetter(false, false) +} + +export const shallowCollectionHandlers: ProxyHandler = { + get: createInstrumentationGetter(false, true) } export const readonlyCollectionHandlers: ProxyHandler = { - get: createInstrumentationGetter(true) + get: createInstrumentationGetter(true, false) } function checkIdentityKeys( diff --git a/packages/reactivity/src/reactive.ts b/packages/reactivity/src/reactive.ts index ac96748f524..67dadf09884 100644 --- a/packages/reactivity/src/reactive.ts +++ b/packages/reactivity/src/reactive.ts @@ -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(target: T): T { target, false, shallowReactiveHandlers, - mutableCollectionHandlers + shallowCollectionHandlers ) }