diff --git a/src/reactivity/reactive.ts b/src/reactivity/reactive.ts index 4cc72803..38469e2e 100644 --- a/src/reactivity/reactive.ts +++ b/src/reactivity/reactive.ts @@ -101,7 +101,7 @@ export function defineAccessControl(target: AnyObject, key: any, val?: any) { }) } -function observe(obj: T): T { +export function observe(obj: T): T { const Vue = getRegisteredVueOrDefault() let observed: T if (Vue.observable) { diff --git a/src/reactivity/readonly.ts b/src/reactivity/readonly.ts index dc4bcbd0..e223c91d 100644 --- a/src/reactivity/readonly.ts +++ b/src/reactivity/readonly.ts @@ -1,7 +1,8 @@ import { reactive, Ref, UnwrapRef } from '.' import { isArray, isPlainObject, warn } from '../utils' import { readonlySet } from '../utils/sets' -import { isRef } from './ref' +import { isReactive, observe } from './reactive' +import { isRef, RefImpl } from './ref' export function isReadonly(obj: any): boolean { return readonlySet.has(obj) @@ -55,8 +56,11 @@ export function shallowReadonly(obj: any): any { return obj } - const readonlyObj = {} - + const readonlyObj = isRef(obj) + ? new RefImpl({} as any) + : isReactive(obj) + ? observe({}) + : {} const source = reactive({}) const ob = (source as any).__ob__ @@ -64,8 +68,8 @@ export function shallowReadonly(obj: any): any { let val = obj[key] let getter: (() => any) | undefined const property = Object.getOwnPropertyDescriptor(obj, key) - if (property && !isRef(obj)) { - if (property.configurable === false) { + if (property) { + if (property.configurable === false && !isRef(obj)) { continue } getter = property.get diff --git a/src/reactivity/ref.ts b/src/reactivity/ref.ts index ccef2a8b..ce8458ac 100644 --- a/src/reactivity/ref.ts +++ b/src/reactivity/ref.ts @@ -71,7 +71,7 @@ interface RefOption { get(): T set?(x: T): void } -class RefImpl implements Ref { +export class RefImpl implements Ref { readonly [_refBrand]!: true public value!: T constructor({ get, set }: RefOption) { diff --git a/test/v3/reactivity/readonly.spec.ts b/test/v3/reactivity/readonly.spec.ts index 55131305..5272f480 100644 --- a/test/v3/reactivity/readonly.spec.ts +++ b/test/v3/reactivity/readonly.spec.ts @@ -1,5 +1,12 @@ import { mockWarn } from '../../helpers/mockWarn' -import { shallowReadonly, isReactive, ref, reactive } from '../../../src' +import { + shallowReadonly, + isReactive, + ref, + reactive, + watch, + nextTick, +} from '../../../src' const Vue = require('vue/dist/vue.common.js') @@ -391,7 +398,7 @@ describe('reactivity/readonly', () => { countReadonly.number++ // @ts-expect-error countRefReadonly.value++ - }, 2000) + }, 1000) return { count, countRef, @@ -401,5 +408,61 @@ describe('reactivity/readonly', () => { expect(vm.$el.textContent).toBe(`{\n "number": 0\n} 0`) }) + + // #712 + test('watch should work for ref with shallowReadonly', async () => { + const cb = jest.fn() + const vm = new Vue({ + template: '
{{ countRef }}
', + setup() { + const countRef = ref(0) + const countRefReadonly = shallowReadonly(countRef) + watch(countRefReadonly, cb, { deep: true }) + return { + countRef, + countRefReadonly, + } + }, + }).$mount() + + vm.countRef++ + await nextTick() + expect(cb).toHaveBeenCalled() + + vm.countRefReadonly++ + await nextTick() + expect( + `Set operation on key "value" failed: target is readonly.` + ).toHaveBeenWarned() + expect(vm.$el.textContent).toBe(`1`) + }) + + // #712 + test('watch should work for reactive with shallowReadonly', async () => { + const cb = jest.fn() + const vm = new Vue({ + template: '
{{ count.number }}
', + setup() { + const count = reactive({ number: 0 }) + const countReadonly = shallowReadonly(count) + watch(countReadonly, cb, { deep: true }) + return { + count, + countReadonly, + } + }, + }).$mount() + + vm.count.number++ + await nextTick() + expect(cb).toHaveBeenCalled() + + vm.countReadonly.number++ + await nextTick() + expect( + `Set operation on key "number" failed: target is readonly.` + ).toHaveBeenWarned() + expect(vm.$el.textContent).toBe(`1`) + }) }) })