fix(reactivity): use WeakMap for proxy/raw checks, compat with non-extensible objects

fix #12799
close #12798
This commit is contained in:
Evan You 2022-10-11 12:26:19 +08:00
parent 27eed829cc
commit 4a0d88e46e
5 changed files with 28 additions and 8 deletions

View File

@ -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)
}

View File

@ -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<T>(observed: T): T {
export function markRaw<T extends object>(
value: T
): T & { [RawSymbol]?: true } {
def(value, ReactiveFlags.SKIP, true)
if (isObject(value)) {
rawMap.set(value, true)
}
return value
}

View File

@ -32,8 +32,8 @@ export type DeepReadonly<T> = T extends Builtin
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: Readonly<T>
const rawToReadonlyFlag = `__v_rawToReadonly`
const rawToShallowReadonlyFlag = `__v_rawToShallowReadonly`
const rawToReadonlyMap = new WeakMap()
const rawToShallowReadonlyMap = new WeakMap()
export function readonly<T extends object>(
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)

View File

@ -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 }),

View File

@ -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)
})
})