]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix: shallowReadonly should keep reactive properties reactive
authorEvan You <yyx990803@gmail.com>
Fri, 20 Dec 2019 16:14:07 +0000 (11:14 -0500)
committerEvan You <yyx990803@gmail.com>
Fri, 20 Dec 2019 16:14:07 +0000 (11:14 -0500)
ref #552

packages/reactivity/__tests__/readonly.spec.ts
packages/reactivity/src/baseHandlers.ts

index 57d286d043ce2a6e09c85be3f3c3c4c966dfb1cb..bcd1c43ec7fb94ec727e49e72153b346acede7e8 100644 (file)
@@ -470,5 +470,13 @@ describe('reactivity/readonly', () => {
         `Set operation on key "foo" failed: target is readonly.`
       ).not.toHaveBeenWarned()
     })
+
+    test('should keep reactive properties reactive', () => {
+      const props: any = shallowReadonly({ n: reactive({ foo: 1 }) })
+      unlock()
+      props.n = reactive({ foo: 2 })
+      lock()
+      expect(isReactive(props.n)).toBe(true)
+    })
   })
 })
index 1b06bb0dbbff6624a4d546302c74be4ecb4021c7..f308c5962cccf51f291ede31e4d5b12c4d9cc0c3 100644 (file)
@@ -11,7 +11,11 @@ const builtInSymbols = new Set(
     .filter(isSymbol)
 )
 
-function createGetter(isReadonly: boolean, shallow = false) {
+const get = createGetter()
+const readonlyGet = createGetter(true)
+const shallowReadonlyGet = createGetter(true, true)
+
+function createGetter(isReadonly = false, shallow = false) {
   return function get(target: object, key: string | symbol, receiver: object) {
     const res = Reflect.get(target, key, receiver)
     if (isSymbol(key) && builtInSymbols.has(key)) {
@@ -36,39 +40,60 @@ function createGetter(isReadonly: boolean, shallow = false) {
   }
 }
 
-function set(
-  target: object,
-  key: string | symbol,
-  value: unknown,
-  receiver: object
-): boolean {
-  value = toRaw(value)
-  const oldValue = (target as any)[key]
-  if (isRef(oldValue) && !isRef(value)) {
-    oldValue.value = value
-    return true
-  }
-  const hadKey = hasOwn(target, key)
-  const result = Reflect.set(target, key, value, receiver)
-  // don't trigger if target is something up in the prototype chain of original
-  if (target === toRaw(receiver)) {
-    /* istanbul ignore else */
-    if (__DEV__) {
-      const extraInfo = { oldValue, newValue: value }
-      if (!hadKey) {
-        trigger(target, TriggerOpTypes.ADD, key, extraInfo)
-      } else if (hasChanged(value, oldValue)) {
-        trigger(target, TriggerOpTypes.SET, key, extraInfo)
+const set = createSetter()
+const readonlySet = createSetter(true)
+const shallowReadonlySet = createSetter(true, true)
+
+function createSetter(isReadonly = false, shallow = false) {
+  return function set(
+    target: object,
+    key: string | symbol,
+    value: unknown,
+    receiver: object
+  ): boolean {
+    if (isReadonly && LOCKED) {
+      if (__DEV__) {
+        console.warn(
+          `Set operation on key "${String(key)}" failed: target is readonly.`,
+          target
+        )
+      }
+      return true
+    }
+
+    const oldValue = (target as any)[key]
+    if (!shallow) {
+      value = toRaw(value)
+      if (isRef(oldValue) && !isRef(value)) {
+        oldValue.value = value
+        return true
       }
     } else {
-      if (!hadKey) {
-        trigger(target, TriggerOpTypes.ADD, key)
-      } else if (hasChanged(value, oldValue)) {
-        trigger(target, TriggerOpTypes.SET, key)
+      // in shallow mode, objects are set as-is regardless of reactive or not
+    }
+
+    const hadKey = hasOwn(target, key)
+    const result = Reflect.set(target, key, value, receiver)
+    // don't trigger if target is something up in the prototype chain of original
+    if (target === toRaw(receiver)) {
+      /* istanbul ignore else */
+      if (__DEV__) {
+        const extraInfo = { oldValue, newValue: value }
+        if (!hadKey) {
+          trigger(target, TriggerOpTypes.ADD, key, extraInfo)
+        } else if (hasChanged(value, oldValue)) {
+          trigger(target, TriggerOpTypes.SET, key, extraInfo)
+        }
+      } else {
+        if (!hadKey) {
+          trigger(target, TriggerOpTypes.ADD, key)
+        } else if (hasChanged(value, oldValue)) {
+          trigger(target, TriggerOpTypes.SET, key)
+        }
       }
     }
+    return result
   }
-  return result
 }
 
 function deleteProperty(target: object, key: string | symbol): boolean {
@@ -98,7 +123,7 @@ function ownKeys(target: object): (string | number | symbol)[] {
 }
 
 export const mutableHandlers: ProxyHandler<object> = {
-  get: createGetter(false),
+  get,
   set,
   deleteProperty,
   has,
@@ -106,27 +131,10 @@ export const mutableHandlers: ProxyHandler<object> = {
 }
 
 export const readonlyHandlers: ProxyHandler<object> = {
-  get: createGetter(true),
-
-  set(
-    target: object,
-    key: string | symbol,
-    value: unknown,
-    receiver: object
-  ): boolean {
-    if (LOCKED) {
-      if (__DEV__) {
-        console.warn(
-          `Set operation on key "${String(key)}" failed: target is readonly.`,
-          target
-        )
-      }
-      return true
-    } else {
-      return set(target, key, value, receiver)
-    }
-  },
-
+  get: readonlyGet,
+  set: readonlySet,
+  has,
+  ownKeys,
   deleteProperty(target: object, key: string | symbol): boolean {
     if (LOCKED) {
       if (__DEV__) {
@@ -141,10 +149,7 @@ export const readonlyHandlers: ProxyHandler<object> = {
     } else {
       return deleteProperty(target, key)
     }
-  },
-
-  has,
-  ownKeys
+  }
 }
 
 // props handlers are special in the sense that it should not unwrap top-level
@@ -152,5 +157,6 @@ export const readonlyHandlers: ProxyHandler<object> = {
 // retain the reactivity of the normal readonly object.
 export const shallowReadonlyHandlers: ProxyHandler<object> = {
   ...readonlyHandlers,
-  get: createGetter(true, true)
+  get: shallowReadonlyGet,
+  set: shallowReadonlySet
 }