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