Browse Source

fix(reactivity): fix shallowReactive nested ref setting edge cases

ref #12688
Evan You 3 năm trước cách đây
mục cha
commit
2af751b6ef

+ 1 - 1
src/core/observer/index.ts

@@ -191,7 +191,7 @@ export function defineReactive(
       } else if (getter) {
         // #7981: for accessor properties without setter
         return
-      } else if (isRef(value) && !isRef(newVal)) {
+      } else if (!shallow && isRef(value) && !isRef(newVal)) {
         value.value = newVal
         return
       } else {

+ 10 - 5
test/unit/features/v3/reactivity/readonly.spec.ts

@@ -483,7 +483,6 @@ describe('reactivity/readonly', () => {
     const ror = readonly(r)
     const obj = reactive({ ror })
     obj.ror = true
-
     expect(obj.ror).toBe(false)
     expect(`Set operation on key "value" failed`).toHaveBeenWarned()
   })
@@ -492,14 +491,20 @@ describe('reactivity/readonly', () => {
     const r = ref(false)
     const ror = readonly(r)
     const obj = reactive({ ror })
-    try {
-      obj.ror = ref(true) as unknown as boolean
-    } catch (e) {}
-
+    obj.ror = ref(true) as unknown as boolean
     expect(obj.ror).toBe(true)
     expect(toRaw(obj).ror).not.toBe(ror) // ref successfully replaced
   })
 
+  test('setting readonly object to writable nested ref', () => {
+    const r = ref<any>()
+    const obj = reactive({ r })
+    const ro = readonly({})
+    obj.r = ro
+    expect(obj.r).toBe(ro)
+    expect(r.value).toBe(ro)
+  })
+
   test('compatiblity with classes', () => {
     const spy = vi.fn()
     class Foo {

+ 15 - 1
test/unit/features/v3/reactivity/ref.spec.ts

@@ -11,7 +11,8 @@ import {
   isReactive,
   isShallow,
   reactive,
-  computed
+  computed,
+  readonly
 } from 'v3'
 import { effect } from 'v3/reactivity/effect'
 
@@ -404,4 +405,17 @@ describe('reactivity/ref', () => {
     b.value = obj
     expect(spy2).toBeCalledTimes(1)
   })
+
+  test('ref should preserve value readonly-ness', () => {
+    const original = {}
+    const r = reactive(original)
+    const rr = readonly(original)
+    const a = ref(original)
+
+    expect(a.value).toBe(r)
+
+    a.value = rr
+    expect(a.value).toBe(rr)
+    expect(a.value).not.toBe(r)
+  })
 })

+ 13 - 0
test/unit/features/v3/reactivity/shallowReactive.spec.ts

@@ -3,6 +3,7 @@ import {
   isRef,
   isShallow,
   reactive,
+  Ref,
   ref,
   shallowReactive,
   shallowReadonly
@@ -45,6 +46,18 @@ describe('shallowReactive', () => {
     expect(foo.bar.value).toBe(123)
   })
 
+  // #12688
+  test('should not mutate refs', () => {
+    const original = ref(123)
+    const foo = shallowReactive<{ bar: Ref<number> | number }>({
+      bar: original
+    })
+    expect(foo.bar).toBe(original)
+    foo.bar = 234
+    expect(foo.bar).toBe(234)
+    expect(original.value).toBe(123)
+  })
+
   // @discrepancy no shallow/non-shallow versions from the same source -
   // cannot support this without real proxies
   // #2843