From 6ecbd5ce2a7f59314a8326a1d193874b87f4d8c8 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 7 Dec 2023 13:26:30 +0800 Subject: [PATCH] fix(reactivity): fix mutation on user proxy of reactive Array close #9742 close #9751 close #9750 --- .../reactivity/__tests__/reactive.spec.ts | 30 ++++++++++++++--- .../__tests__/reactiveArray.spec.ts | 9 ++++++ packages/reactivity/src/baseHandlers.ts | 32 +++++++++++-------- 3 files changed, 54 insertions(+), 17 deletions(-) diff --git a/packages/reactivity/__tests__/reactive.spec.ts b/packages/reactivity/__tests__/reactive.spec.ts index e7fe18252ab..1ea1ba4a901 100644 --- a/packages/reactivity/__tests__/reactive.spec.ts +++ b/packages/reactivity/__tests__/reactive.spec.ts @@ -158,6 +158,21 @@ describe('reactivity/reactive', () => { expect(original.bar).toBe(original2) }) + // #1246 + test('mutation on objects using reactive as prototype should not trigger', () => { + const observed = reactive({ foo: 1 }) + const original = Object.create(observed) + let dummy + effect(() => (dummy = original.foo)) + expect(dummy).toBe(1) + observed.foo = 2 + expect(dummy).toBe(2) + original.foo = 3 + expect(dummy).toBe(2) + original.foo = 4 + expect(dummy).toBe(2) + }) + test('toRaw', () => { const original = { foo: 1 } const observed = reactive(original) @@ -166,11 +181,18 @@ describe('reactivity/reactive', () => { }) test('toRaw on object using reactive as prototype', () => { - const original = reactive({}) - const obj = Object.create(original) + const original = { foo: 1 } + const observed = reactive(original) + const inherted = Object.create(observed) + expect(toRaw(inherted)).toBe(inherted) + }) + + test('toRaw on user Proxy wrapping reactive', () => { + const original = {} + const re = reactive(original) + const obj = new Proxy(re, {}) const raw = toRaw(obj) - expect(raw).toBe(obj) - expect(raw).not.toBe(toRaw(original)) + expect(raw).toBe(original) }) test('should not unwrap Ref', () => { diff --git a/packages/reactivity/__tests__/reactiveArray.spec.ts b/packages/reactivity/__tests__/reactiveArray.spec.ts index 808c5aa5529..30189684395 100644 --- a/packages/reactivity/__tests__/reactiveArray.spec.ts +++ b/packages/reactivity/__tests__/reactiveArray.spec.ts @@ -142,6 +142,15 @@ describe('reactivity/reactive/Array', () => { expect(length).toBe('01') }) + // #9742 + test('mutation on user proxy of reactive Array', () => { + const array = reactive([]) + const proxy = new Proxy(array, {}) + proxy.push(1) + expect(array).toHaveLength(1) + expect(proxy).toHaveLength(1) + }) + describe('Array methods w/ refs', () => { let original: any[] beforeEach(() => { diff --git a/packages/reactivity/src/baseHandlers.ts b/packages/reactivity/src/baseHandlers.ts index fa7ebbb7d75..7a73a09acdd 100644 --- a/packages/reactivity/src/baseHandlers.ts +++ b/packages/reactivity/src/baseHandlers.ts @@ -100,19 +100,25 @@ class BaseReactiveHandler implements ProxyHandler { return isReadonly } else if (key === ReactiveFlags.IS_SHALLOW) { return shallow - } else if ( - key === ReactiveFlags.RAW && - receiver === - (isReadonly - ? shallow - ? shallowReadonlyMap - : readonlyMap - : shallow - ? shallowReactiveMap - : reactiveMap - ).get(target) - ) { - return target + } else if (key === ReactiveFlags.RAW) { + if ( + receiver === + (isReadonly + ? shallow + ? shallowReadonlyMap + : readonlyMap + : shallow + ? shallowReactiveMap + : reactiveMap + ).get(target) || + // receiver is not the reactive proxy, but has the same prototype + // this means the reciever is a user proxy of the reactive proxy + Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver) + ) { + return target + } + // early return undefined + return } const targetIsArray = isArray(target)