fix(reactivity): fix iOS 12 JSON.stringify error on reactive objects
- Use WeakMap for raw -> reactive/readonly storage. This is slightly more expensive than using a field on the taget object but avoids polluting the original. - also fix Collection.forEach callback value fix #1916
This commit is contained in:
parent
410e7abbbb
commit
016ba116a8
@ -228,7 +228,7 @@ describe('reactivity/readonly', () => {
|
||||
test('should retrieve readonly values on iteration', () => {
|
||||
const key1 = {}
|
||||
const key2 = {}
|
||||
const original = new Collection([[key1, {}], [key2, {}]])
|
||||
const original = new Map([[key1, {}], [key2, {}]])
|
||||
const wrapped: any = readonly(original)
|
||||
expect(wrapped.size).toBe(2)
|
||||
for (const [key, value] of wrapped) {
|
||||
@ -246,7 +246,7 @@ describe('reactivity/readonly', () => {
|
||||
test('should retrieve reactive + readonly values on iteration', () => {
|
||||
const key1 = {}
|
||||
const key2 = {}
|
||||
const original = reactive(new Collection([[key1, {}], [key2, {}]]))
|
||||
const original = reactive(new Map([[key1, {}], [key2, {}]]))
|
||||
const wrapped: any = readonly(original)
|
||||
expect(wrapped.size).toBe(2)
|
||||
for (const [key, value] of wrapped) {
|
||||
|
@ -1,4 +1,12 @@
|
||||
import { reactive, readonly, toRaw, ReactiveFlags, Target } from './reactive'
|
||||
import {
|
||||
reactive,
|
||||
readonly,
|
||||
toRaw,
|
||||
ReactiveFlags,
|
||||
Target,
|
||||
readonlyMap,
|
||||
reactiveMap
|
||||
} from './reactive'
|
||||
import { TrackOpTypes, TriggerOpTypes } from './operations'
|
||||
import { track, trigger, ITERATE_KEY } from './effect'
|
||||
import {
|
||||
@ -48,10 +56,7 @@ function createGetter(isReadonly = false, shallow = false) {
|
||||
return isReadonly
|
||||
} else if (
|
||||
key === ReactiveFlags.RAW &&
|
||||
receiver ===
|
||||
(isReadonly
|
||||
? target[ReactiveFlags.READONLY]
|
||||
: target[ReactiveFlags.REACTIVE])
|
||||
receiver === (isReadonly ? readonlyMap : reactiveMap).get(target)
|
||||
) {
|
||||
return target
|
||||
}
|
||||
|
@ -145,17 +145,17 @@ function createForEach(isReadonly: boolean, isShallow: boolean) {
|
||||
callback: Function,
|
||||
thisArg?: unknown
|
||||
) {
|
||||
const observed = this
|
||||
const target = toRaw(observed)
|
||||
const observed = this as any
|
||||
const target = observed[ReactiveFlags.RAW]
|
||||
const rawTarget = toRaw(target)
|
||||
const wrap = isReadonly ? toReadonly : isShallow ? toShallow : toReactive
|
||||
!isReadonly && track(target, TrackOpTypes.ITERATE, ITERATE_KEY)
|
||||
// important: create sure the callback is
|
||||
// 1. invoked with the reactive map as `this` and 3rd arg
|
||||
// 2. the value received should be a corresponding reactive/readonly.
|
||||
function wrappedCallback(value: unknown, key: unknown) {
|
||||
!isReadonly && track(rawTarget, TrackOpTypes.ITERATE, ITERATE_KEY)
|
||||
return target.forEach((value: unknown, key: unknown) => {
|
||||
// important: make sure the callback is
|
||||
// 1. invoked with the reactive map as `this` and 3rd arg
|
||||
// 2. the value received should be a corresponding reactive/readonly.
|
||||
return callback.call(thisArg, wrap(value), wrap(key), observed)
|
||||
}
|
||||
return getProto(target).forEach.call(target, wrappedCallback)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { isObject, toRawType, def, hasOwn } from '@vue/shared'
|
||||
import { isObject, toRawType, def } from '@vue/shared'
|
||||
import {
|
||||
mutableHandlers,
|
||||
readonlyHandlers,
|
||||
@ -16,9 +16,7 @@ export const enum ReactiveFlags {
|
||||
SKIP = '__v_skip',
|
||||
IS_REACTIVE = '__v_isReactive',
|
||||
IS_READONLY = '__v_isReadonly',
|
||||
RAW = '__v_raw',
|
||||
REACTIVE = '__v_reactive',
|
||||
READONLY = '__v_readonly'
|
||||
RAW = '__v_raw'
|
||||
}
|
||||
|
||||
export interface Target {
|
||||
@ -26,10 +24,11 @@ export interface Target {
|
||||
[ReactiveFlags.IS_REACTIVE]?: boolean
|
||||
[ReactiveFlags.IS_READONLY]?: boolean
|
||||
[ReactiveFlags.RAW]?: any
|
||||
[ReactiveFlags.REACTIVE]?: any
|
||||
[ReactiveFlags.READONLY]?: any
|
||||
}
|
||||
|
||||
export const reactiveMap = new WeakMap<Target, any>()
|
||||
export const readonlyMap = new WeakMap<Target, any>()
|
||||
|
||||
const enum TargetType {
|
||||
INVALID = 0,
|
||||
COMMON = 1,
|
||||
@ -155,23 +154,22 @@ function createReactiveObject(
|
||||
return target
|
||||
}
|
||||
// target already has corresponding Proxy
|
||||
const reactiveFlag = isReadonly
|
||||
? ReactiveFlags.READONLY
|
||||
: ReactiveFlags.REACTIVE
|
||||
if (hasOwn(target, reactiveFlag)) {
|
||||
return target[reactiveFlag]
|
||||
const proxyMap = isReadonly ? readonlyMap : reactiveMap
|
||||
const existingProxy = proxyMap.get(target)
|
||||
if (existingProxy) {
|
||||
return existingProxy
|
||||
}
|
||||
// only a whitelist of value types can be observed.
|
||||
const targetType = getTargetType(target)
|
||||
if (targetType === TargetType.INVALID) {
|
||||
return target
|
||||
}
|
||||
const observed = new Proxy(
|
||||
const proxy = new Proxy(
|
||||
target,
|
||||
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
|
||||
)
|
||||
def(target, reactiveFlag, observed)
|
||||
return observed
|
||||
proxyMap.set(target, proxy)
|
||||
return proxy
|
||||
}
|
||||
|
||||
export function isReactive(value: unknown): boolean {
|
||||
|
Loading…
Reference in New Issue
Block a user