]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(reactivity): shallowReactive for collections (#1204)
authorCarlos Rodrigues <carlos@hypermob.co.uk>
Mon, 18 May 2020 15:17:37 +0000 (16:17 +0100)
committerGitHub <noreply@github.com>
Mon, 18 May 2020 15:17:37 +0000 (11:17 -0400)
close #1202

packages/reactivity/__tests__/reactive.spec.ts
packages/reactivity/src/collectionHandlers.ts
packages/reactivity/src/reactive.ts

index 44fc5391df1cf35efa8f655ecaf131401a507dc1..6d2c68962d465fa06ee233e75ecafd3d1f69bb26 100644 (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))
+    })
   })
 })
index 5aff152e939d7e6edf0ae74669f67c5037ee9efd..181a6e2152cebe8e698bbe37fc6e247d410d55f4 100644 (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(
index ac96748f524be5c7c03e45d645e6630769e4837e..67dadf098846b7e226fa320129a7acbc53a48674 100644 (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
   )
 }