fix(reactivity): fix shallow/readonly edge cases
This commit is contained in:
parent
8dcb6c7bbd
commit
a95554d35c
@ -480,21 +480,27 @@ describe('reactivity/readonly', () => {
|
|||||||
const r = ref(false)
|
const r = ref(false)
|
||||||
const ror = readonly(r)
|
const ror = readonly(r)
|
||||||
const obj = reactive({ ror })
|
const obj = reactive({ ror })
|
||||||
try {
|
expect(() => {
|
||||||
obj.ror = true
|
obj.ror = true
|
||||||
} catch (e) {}
|
}).toThrow()
|
||||||
|
|
||||||
expect(obj.ror).toBe(false)
|
expect(obj.ror).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('replacing a readonly ref nested in a reactive object with a new ref', () => {
|
test('replacing a readonly ref nested in a reactive object with a new ref', () => {
|
||||||
const r = ref(false)
|
const r = ref(false)
|
||||||
const ror = readonly(r)
|
const ror = readonly(r)
|
||||||
const obj = reactive({ ror })
|
const obj = reactive({ ror })
|
||||||
try {
|
|
||||||
obj.ror = ref(true) as unknown as boolean
|
obj.ror = ref(true) as unknown as boolean
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
expect(obj.ror).toBe(true)
|
expect(obj.ror).toBe(true)
|
||||||
expect(toRaw(obj).ror).not.toBe(ror) // ref successfully replaced
|
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)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
} from '../src/index'
|
} from '../src/index'
|
||||||
import { computed } from '@vue/runtime-dom'
|
import { computed } from '@vue/runtime-dom'
|
||||||
import { shallowRef, unref, customRef, triggerRef } from '../src/ref'
|
import { shallowRef, unref, customRef, triggerRef } from '../src/ref'
|
||||||
import { isShallow } from '../src/reactive'
|
import { isShallow, readonly, shallowReactive } from '../src/reactive'
|
||||||
|
|
||||||
describe('reactivity/ref', () => {
|
describe('reactivity/ref', () => {
|
||||||
it('should hold a value', () => {
|
it('should hold a value', () => {
|
||||||
@ -400,4 +400,22 @@ describe('reactivity/ref', () => {
|
|||||||
b.value = obj
|
b.value = obj
|
||||||
expect(spy2).toBeCalledTimes(1)
|
expect(spy2).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('ref should preserve value shallow/readonly-ness', () => {
|
||||||
|
const original = {}
|
||||||
|
const r = reactive(original)
|
||||||
|
const s = shallowReactive(original)
|
||||||
|
const rr = readonly(original)
|
||||||
|
const a = ref(original)
|
||||||
|
|
||||||
|
expect(a.value).toBe(r)
|
||||||
|
|
||||||
|
a.value = s
|
||||||
|
expect(a.value).toBe(s)
|
||||||
|
expect(a.value).not.toBe(r)
|
||||||
|
|
||||||
|
a.value = rr
|
||||||
|
expect(a.value).toBe(rr)
|
||||||
|
expect(a.value).not.toBe(r)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
} from '../src/reactive'
|
} from '../src/reactive'
|
||||||
|
|
||||||
import { effect } from '../src/effect'
|
import { effect } from '../src/effect'
|
||||||
|
import { Ref, isRef, ref } from '../src/ref'
|
||||||
|
|
||||||
describe('shallowReactive', () => {
|
describe('shallowReactive', () => {
|
||||||
test('should not make non-reactive properties reactive', () => {
|
test('should not make non-reactive properties reactive', () => {
|
||||||
@ -46,6 +47,27 @@ describe('shallowReactive', () => {
|
|||||||
expect(isReactive(r.foo.bar)).toBe(false)
|
expect(isReactive(r.foo.bar)).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// vuejs/vue#12597
|
||||||
|
test('should not unwrap refs', () => {
|
||||||
|
const foo = shallowReactive({
|
||||||
|
bar: ref(123)
|
||||||
|
})
|
||||||
|
expect(isRef(foo.bar)).toBe(true)
|
||||||
|
expect(foo.bar.value).toBe(123)
|
||||||
|
})
|
||||||
|
|
||||||
|
// vuejs/vue#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)
|
||||||
|
})
|
||||||
|
|
||||||
test('should respect shallow/deep versions of same target on access', () => {
|
test('should respect shallow/deep versions of same target on access', () => {
|
||||||
const original = {}
|
const original = {}
|
||||||
const shallow = shallowReactive(original)
|
const shallow = shallowReactive(original)
|
||||||
|
@ -158,10 +158,10 @@ function createSetter(shallow = false) {
|
|||||||
if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
|
if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (!shallow && !isReadonly(value)) {
|
if (!shallow) {
|
||||||
if (!isShallow(value)) {
|
if (!isShallow(value) && !isReadonly(value)) {
|
||||||
value = toRaw(value)
|
|
||||||
oldValue = toRaw(oldValue)
|
oldValue = toRaw(oldValue)
|
||||||
|
value = toRaw(value)
|
||||||
}
|
}
|
||||||
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
|
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
|
||||||
oldValue.value = value
|
oldValue.value = value
|
||||||
|
@ -6,7 +6,14 @@ import {
|
|||||||
} from './effect'
|
} from './effect'
|
||||||
import { TrackOpTypes, TriggerOpTypes } from './operations'
|
import { TrackOpTypes, TriggerOpTypes } from './operations'
|
||||||
import { isArray, hasChanged, IfAny } from '@vue/shared'
|
import { isArray, hasChanged, IfAny } from '@vue/shared'
|
||||||
import { isProxy, toRaw, isReactive, toReactive } from './reactive'
|
import {
|
||||||
|
isProxy,
|
||||||
|
toRaw,
|
||||||
|
isReactive,
|
||||||
|
toReactive,
|
||||||
|
isReadonly,
|
||||||
|
isShallow
|
||||||
|
} from './reactive'
|
||||||
import type { ShallowReactiveMarker } from './reactive'
|
import type { ShallowReactiveMarker } from './reactive'
|
||||||
import { CollectionTypes } from './collectionHandlers'
|
import { CollectionTypes } from './collectionHandlers'
|
||||||
import { createDep, Dep } from './dep'
|
import { createDep, Dep } from './dep'
|
||||||
@ -112,10 +119,12 @@ class RefImpl<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
set value(newVal) {
|
set value(newVal) {
|
||||||
newVal = this.__v_isShallow ? newVal : toRaw(newVal)
|
const useDirectValue =
|
||||||
|
this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
|
||||||
|
newVal = useDirectValue ? newVal : toRaw(newVal)
|
||||||
if (hasChanged(newVal, this._rawValue)) {
|
if (hasChanged(newVal, this._rawValue)) {
|
||||||
this._rawValue = newVal
|
this._rawValue = newVal
|
||||||
this._value = this.__v_isShallow ? newVal : toReactive(newVal)
|
this._value = useDirectValue ? newVal : toReactive(newVal)
|
||||||
triggerRefValue(this, newVal)
|
triggerRefValue(this, newVal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user