Explorar o código

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

close #1202
Carlos Rodrigues %!s(int64=6) %!d(string=hai) anos
pai
achega
488e2bcfef

+ 27 - 0
packages/reactivity/__tests__/reactive.spec.ts

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

+ 47 - 13
packages/reactivity/src/collectionHandlers.ts

@@ -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) {
-  const instrumentations = isReadonly
-    ? readonlyInstrumentations
-    : mutableInstrumentations
+function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
+  const instrumentations = shallow
+    ? shallowInstrumentations
+    : 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(

+ 3 - 2
packages/reactivity/src/reactive.ts

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