diff --git a/packages/reactivity/__tests__/readonly.spec.ts b/packages/reactivity/__tests__/readonly.spec.ts index b9edb440fb7..097c76dddd7 100644 --- a/packages/reactivity/__tests__/readonly.spec.ts +++ b/packages/reactivity/__tests__/readonly.spec.ts @@ -8,7 +8,8 @@ import { effect, ref, shallowReadonly, - isProxy + isProxy, + computed } from '../src' /** @@ -435,6 +436,26 @@ describe('reactivity/readonly', () => { ).toHaveBeenWarned() }) + // https://github.com/vuejs/vue-next/issues/3376 + test('calling readonly on computed should allow computed to set its private properties', () => { + const r = ref(false) + const c = computed(() => r.value) + const rC = readonly(c) + + r.value = true + + expect(rC.value).toBe(true) + expect( + 'Set operation on key "_dirty" failed: target is readonly.' + ).not.toHaveBeenWarned() + // @ts-expect-error - non-existant property + rC.randomProperty = true + + expect( + 'Set operation on key "randomProperty" failed: target is readonly.' + ).toHaveBeenWarned() + }) + describe('shallowReadonly', () => { test('should not make non-reactive properties reactive', () => { const props = shallowReadonly({ n: { foo: 1 } }) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 7f2c5b50d80..c12f3e55d94 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -48,12 +48,14 @@ class ComputedRefImpl { } get value() { - if (this._dirty) { - this._value = this.effect() - this._dirty = false + // the computed ref may get wrapped by other proxies e.g. readonly() #3376 + const self = toRaw(this) + if (self._dirty) { + self._value = this.effect() + self._dirty = false } - track(toRaw(this), TrackOpTypes.GET, 'value') - return this._value + track(self, TrackOpTypes.GET, 'value') + return self._value } set value(newValue: T) {