From 4a0d88e46e4180edc7f22e36c25df3f8ac5d60d2 Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 11 Oct 2022 12:26:19 +0800 Subject: [PATCH] fix(reactivity): use WeakMap for proxy/raw checks, compat with non-extensible objects fix #12799 close #12798 --- src/core/observer/index.ts | 4 +++- src/v3/reactivity/reactive.ts | 9 +++++++-- src/v3/reactivity/readonly.ts | 10 +++++----- test/unit/features/v3/reactivity/reactive.spec.ts | 6 ++++++ test/unit/features/v3/reactivity/readonly.spec.ts | 7 +++++++ 5 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/core/observer/index.ts b/src/core/observer/index.ts index 3770c489a..dcf6bde1e 100644 --- a/src/core/observer/index.ts +++ b/src/core/observer/index.ts @@ -17,6 +17,7 @@ import { noop } from '../util/index' import { isReadonly, isRef, TrackOpTypes, TriggerOpTypes } from '../../v3' +import { rawMap } from '../../v3/reactivity/reactive' const arrayKeys = Object.getOwnPropertyNames(arrayMethods) @@ -118,7 +119,8 @@ export function observe( (ssrMockReactivity || !isServerRendering()) && (isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && - !value.__v_skip /* ReactiveFlags.SKIP */ + !value.__v_skip /* ReactiveFlags.SKIP */ && + !rawMap.has(value) ) { ob = new Observer(value, shallow, ssrMockReactivity) } diff --git a/src/v3/reactivity/reactive.ts b/src/v3/reactivity/reactive.ts index 0ff682243..c65f25e67 100644 --- a/src/v3/reactivity/reactive.ts +++ b/src/v3/reactivity/reactive.ts @@ -5,10 +5,13 @@ import { isPrimitive, warn, toRawType, - isServerRendering + isServerRendering, + isObject } from 'core/util' import type { Ref, UnwrapRefSimple, RawSymbol } from './ref' +export const rawMap = new WeakMap() + export const enum ReactiveFlags { SKIP = '__v_skip', IS_READONLY = '__v_isReadonly', @@ -119,7 +122,9 @@ export function toRaw(observed: T): T { export function markRaw( value: T ): T & { [RawSymbol]?: true } { - def(value, ReactiveFlags.SKIP, true) + if (isObject(value)) { + rawMap.set(value, true) + } return value } diff --git a/src/v3/reactivity/readonly.ts b/src/v3/reactivity/readonly.ts index 933f910b6..c3794bb53 100644 --- a/src/v3/reactivity/readonly.ts +++ b/src/v3/reactivity/readonly.ts @@ -32,8 +32,8 @@ export type DeepReadonly = T extends Builtin ? { readonly [K in keyof T]: DeepReadonly } : Readonly -const rawToReadonlyFlag = `__v_rawToReadonly` -const rawToShallowReadonlyFlag = `__v_rawToShallowReadonly` +const rawToReadonlyMap = new WeakMap() +const rawToShallowReadonlyMap = new WeakMap() export function readonly( target: T @@ -63,14 +63,14 @@ function createReadonly(target: any, shallow: boolean) { } // already has a readonly proxy - const existingFlag = shallow ? rawToShallowReadonlyFlag : rawToReadonlyFlag - const existingProxy = target[existingFlag] + const map = shallow ? rawToShallowReadonlyMap : rawToReadonlyMap + const existingProxy = map.get(target) if (existingProxy) { return existingProxy } const proxy = Object.create(Object.getPrototypeOf(target)) - def(target, existingFlag, proxy) + map.set(target, proxy) def(proxy, ReactiveFlags.IS_READONLY, true) def(proxy, ReactiveFlags.RAW, target) diff --git a/test/unit/features/v3/reactivity/reactive.spec.ts b/test/unit/features/v3/reactivity/reactive.spec.ts index f636d44d4..a8a30e035 100644 --- a/test/unit/features/v3/reactivity/reactive.spec.ts +++ b/test/unit/features/v3/reactivity/reactive.spec.ts @@ -258,6 +258,12 @@ describe('reactivity/reactive', () => { expect(isReactive(obj.bar)).toBe(false) }) + test('markRaw on non-extensible objects', () => { + const foo = Object.freeze({}) + markRaw(foo) + expect(isReactive(reactive(foo))).toBe(false) + }) + test('should not observe non-extensible objects', () => { const obj = reactive({ foo: Object.preventExtensions({ a: 1 }), diff --git a/test/unit/features/v3/reactivity/readonly.spec.ts b/test/unit/features/v3/reactivity/readonly.spec.ts index 012e142d4..fab99bbfb 100644 --- a/test/unit/features/v3/reactivity/readonly.spec.ts +++ b/test/unit/features/v3/reactivity/readonly.spec.ts @@ -525,4 +525,11 @@ describe('reactivity/readonly', () => { expect(readonlyFoo.x).toBe(1) expect(`et operation on key "x" failed`).toHaveBeenWarned() }) + + test('compatibility with non-extensible objects', () => { + const foo = Object.freeze({ a: 1 }) + const bar = readonly(foo) + expect(isReadonly(bar)).toBe(true) + expect(readonly(foo)).toBe(bar) + }) })