isReactive,
isReadonly,
markNonReactive,
- lock,
- unlock,
effect,
ref,
shallowReadonly
).toHaveBeenWarnedLast()
})
- it('should allow mutation when unlocked', () => {
- const observed: any = readonly({ foo: 1, bar: { baz: 2 } })
- unlock()
- observed.prop = 2
- observed.bar.qux = 3
- delete observed.bar.baz
- delete observed.foo
- lock()
- expect(observed.prop).toBe(2)
- expect(observed.foo).toBeUndefined()
- expect(observed.bar.qux).toBe(3)
- expect('baz' in observed.bar).toBe(false)
- expect(`target is readonly`).not.toHaveBeenWarned()
- })
-
- it('should not trigger effects when locked', () => {
+ it('should not trigger effects', () => {
const observed: any = readonly({ a: 1 })
let dummy
effect(() => {
expect(dummy).toBe(1)
expect(`target is readonly`).toHaveBeenWarned()
})
-
- it('should trigger effects when unlocked', () => {
- const observed: any = readonly({ a: 1 })
- let dummy
- effect(() => {
- dummy = observed.a
- })
- expect(dummy).toBe(1)
- unlock()
- observed.a = 2
- lock()
- expect(observed.a).toBe(2)
- expect(dummy).toBe(2)
- })
})
describe('Array', () => {
expect(`target is readonly.`).toHaveBeenWarnedTimes(5)
})
- it('should allow mutation when unlocked', () => {
- const observed: any = readonly([{ foo: 1, bar: { baz: 2 } }])
- unlock()
- observed[1] = 2
- observed.push(3)
- observed[0].foo = 2
- observed[0].bar.baz = 3
- lock()
- expect(observed.length).toBe(3)
- expect(observed[1]).toBe(2)
- expect(observed[2]).toBe(3)
- expect(observed[0].foo).toBe(2)
- expect(observed[0].bar.baz).toBe(3)
- expect(`target is readonly`).not.toHaveBeenWarned()
- })
-
- it('should not trigger effects when locked', () => {
+ it('should not trigger effects', () => {
const observed: any = readonly([{ a: 1 }])
let dummy
effect(() => {
expect(dummy).toBe(1)
expect(`target is readonly`).toHaveBeenWarnedTimes(2)
})
-
- it('should trigger effects when unlocked', () => {
- const observed: any = readonly([{ a: 1 }])
- let dummy
- effect(() => {
- dummy = observed[0].a
- })
- expect(dummy).toBe(1)
-
- unlock()
-
- observed[0].a = 2
- expect(observed[0].a).toBe(2)
- expect(dummy).toBe(2)
-
- observed[0] = { a: 3 }
- expect(observed[0].a).toBe(3)
- expect(dummy).toBe(3)
-
- observed.unshift({ a: 4 })
- expect(observed[0].a).toBe(4)
- expect(dummy).toBe(4)
- lock()
- })
})
const maps = [Map, WeakMap]
).toHaveBeenWarned()
})
- test('should allow mutation & trigger effect when unlocked', () => {
- const map = readonly(new Collection())
- const isWeak = Collection === WeakMap
- const key = {}
- let dummy
- effect(() => {
- dummy = map.get(key) + (isWeak ? 0 : map.size)
- })
- expect(dummy).toBeNaN()
- unlock()
- map.set(key, 1)
- lock()
- expect(dummy).toBe(isWeak ? 1 : 2)
- expect(map.get(key)).toBe(1)
- expect(`target is readonly`).not.toHaveBeenWarned()
- })
-
if (Collection === Map) {
test('should retrieve readonly values on iteration', () => {
const key1 = {}
).toHaveBeenWarned()
})
- test('should allow mutation & trigger effect when unlocked', () => {
- const set = readonly(new Collection())
- const key = {}
- let dummy
- effect(() => {
- dummy = set.has(key)
- })
- expect(dummy).toBe(false)
- unlock()
- set.add(key)
- lock()
- expect(dummy).toBe(true)
- expect(set.has(key)).toBe(true)
- expect(`target is readonly`).not.toHaveBeenWarned()
- })
-
if (Collection === Set) {
test('should retrieve readonly values on iteration', () => {
const original = new Collection([{}, {}])
expect(toRaw(a)).toBe(toRaw(b))
})
+ test('readonly should track and trigger if wrapping reactive original', () => {
+ const a = reactive({ n: 1 })
+ const b = readonly(a)
+ let dummy
+ effect(() => {
+ dummy = b.n
+ })
+ expect(dummy).toBe(1)
+ a.n++
+ expect(b.n).toBe(2)
+ expect(dummy).toBe(2)
+ })
+
test('observing already observed value should return same Proxy', () => {
const original = { foo: 1 }
const observed = readonly(original)
`Set operation on key "foo" failed: target is readonly.`
).not.toHaveBeenWarned()
})
-
- test('should keep reactive properties reactive', () => {
- const props: any = shallowReadonly({ n: reactive({ foo: 1 }) })
- unlock()
- props.n = reactive({ foo: 2 })
- lock()
- expect(isReactive(props.n)).toBe(true)
- })
})
})
import { reactive, readonly, toRaw } from './reactive'
import { TrackOpTypes, TriggerOpTypes } from './operations'
import { track, trigger, ITERATE_KEY } from './effect'
-import { LOCKED } from './lock'
import { isObject, hasOwn, isSymbol, hasChanged, isArray } from '@vue/shared'
import { isRef } from './ref'
)
const get = /*#__PURE__*/ createGetter()
-const shallowReactiveGet = /*#__PURE__*/ createGetter(false, true)
+const shallowGet = /*#__PURE__*/ createGetter(false, true)
const readonlyGet = /*#__PURE__*/ createGetter(true)
const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true)
return Reflect.get(arrayInstrumentations, key, receiver)
}
const res = Reflect.get(target, key, receiver)
+
if (isSymbol(key) && builtInSymbols.has(key)) {
return res
}
+
if (shallow) {
- track(target, TrackOpTypes.GET, key)
- // TODO strict mode that returns a shallow-readonly version of the value
+ !isReadonly && track(target, TrackOpTypes.GET, key)
return res
}
+
if (isRef(res)) {
if (targetIsArray) {
- track(target, TrackOpTypes.GET, key)
+ !isReadonly && track(target, TrackOpTypes.GET, key)
return res
} else {
// ref unwrapping, only for Objects, not for Arrays.
return res.value
}
- } else {
- track(target, TrackOpTypes.GET, key)
- return isObject(res)
- ? isReadonly
- ? // need to lazy access readonly and reactive here to avoid
- // circular dependency
- readonly(res)
- : reactive(res)
- : res
}
+
+ !isReadonly && track(target, TrackOpTypes.GET, key)
+ return isObject(res)
+ ? isReadonly
+ ? // need to lazy access readonly and reactive here to avoid
+ // circular dependency
+ readonly(res)
+ : reactive(res)
+ : res
}
}
const set = /*#__PURE__*/ createSetter()
-const shallowReactiveSet = /*#__PURE__*/ createSetter(false, true)
-const readonlySet = /*#__PURE__*/ createSetter(true)
-const shallowReadonlySet = /*#__PURE__*/ createSetter(true, true)
+const shallowSet = /*#__PURE__*/ createSetter(true)
-function createSetter(isReadonly = false, shallow = false) {
+function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
- if (isReadonly && LOCKED) {
- if (__DEV__) {
- console.warn(
- `Set operation on key "${String(key)}" failed: target is readonly.`,
- target
- )
- }
- return true
- }
-
const oldValue = (target as any)[key]
if (!shallow) {
value = toRaw(value)
export const readonlyHandlers: ProxyHandler<object> = {
get: readonlyGet,
- set: readonlySet,
has,
ownKeys,
- deleteProperty(target: object, key: string | symbol): boolean {
- if (LOCKED) {
- if (__DEV__) {
- console.warn(
- `Delete operation on key "${String(
- key
- )}" failed: target is readonly.`,
- target
- )
- }
- return true
- } else {
- return deleteProperty(target, key)
+ set(target, key) {
+ if (__DEV__) {
+ console.warn(
+ `Set operation on key "${String(key)}" failed: target is readonly.`,
+ target
+ )
+ }
+ return true
+ },
+ deleteProperty(target, key) {
+ if (__DEV__) {
+ console.warn(
+ `Delete operation on key "${String(key)}" failed: target is readonly.`,
+ target
+ )
}
+ return true
}
}
export const shallowReactiveHandlers: ProxyHandler<object> = {
...mutableHandlers,
- get: shallowReactiveGet,
- set: shallowReactiveSet
+ get: shallowGet,
+ set: shallowSet
}
// Props handlers are special in the sense that it should not unwrap top-level
// retain the reactivity of the normal readonly object.
export const shallowReadonlyHandlers: ProxyHandler<object> = {
...readonlyHandlers,
- get: shallowReadonlyGet,
- set: shallowReadonlySet
+ get: shallowReadonlyGet
}
import { toRaw, reactive, readonly } from './reactive'
import { track, trigger, ITERATE_KEY, MAP_KEY_ITERATE_KEY } from './effect'
import { TrackOpTypes, TriggerOpTypes } from './operations'
-import { LOCKED } from './lock'
import {
isObject,
capitalize,
const observed = this
const target = toRaw(observed)
const wrap = isReadonly ? toReadonly : toReactive
- track(target, TrackOpTypes.ITERATE, ITERATE_KEY)
+ !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.
const isKeyOnly = method === 'keys' && isMap
const innerIterator = getProto(target)[method].apply(target, args)
const wrap = isReadonly ? toReadonly : toReactive
- track(
- target,
- TrackOpTypes.ITERATE,
- isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY
- )
+ !isReadonly &&
+ track(
+ target,
+ TrackOpTypes.ITERATE,
+ isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY
+ )
// return a wrapped iterator which returns observed versions of the
// values emitted from the real iterator
return {
}
}
-function createReadonlyMethod(
- method: Function,
- type: TriggerOpTypes
-): Function {
+function createReadonlyMethod(type: TriggerOpTypes): Function {
return function(this: CollectionTypes, ...args: unknown[]) {
- if (LOCKED) {
- if (__DEV__) {
- const key = args[0] ? `on key "${args[0]}" ` : ``
- console.warn(
- `${capitalize(type)} operation ${key}failed: target is readonly.`,
- toRaw(this)
- )
- }
- return type === TriggerOpTypes.DELETE ? false : this
- } else {
- return method.apply(this, args)
+ if (__DEV__) {
+ const key = args[0] ? `on key "${args[0]}" ` : ``
+ console.warn(
+ `${capitalize(type)} operation ${key}failed: target is readonly.`,
+ toRaw(this)
+ )
}
+ return type === TriggerOpTypes.DELETE ? false : this
}
}
return size((this as unknown) as IterableCollections)
},
has,
- add: createReadonlyMethod(add, TriggerOpTypes.ADD),
- set: createReadonlyMethod(set, TriggerOpTypes.SET),
- delete: createReadonlyMethod(deleteEntry, TriggerOpTypes.DELETE),
- clear: createReadonlyMethod(clear, TriggerOpTypes.CLEAR),
+ add: createReadonlyMethod(TriggerOpTypes.ADD),
+ set: createReadonlyMethod(TriggerOpTypes.SET),
+ delete: createReadonlyMethod(TriggerOpTypes.DELETE),
+ clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
forEach: createForEach(true)
}
ReactiveEffectOptions,
DebuggerEvent
} from './effect'
-export { lock, unlock } from './lock'
export { TrackOpTypes, TriggerOpTypes } from './operations'
+++ /dev/null
-// global immutability lock
-export let LOCKED = true
-
-export function lock() {
- LOCKED = true
-}
-
-export function unlock() {
- LOCKED = false
-}
import {
mutableHandlers,
readonlyHandlers,
- shallowReadonlyHandlers,
- shallowReactiveHandlers
+ shallowReactiveHandlers,
+ shallowReadonlyHandlers
} from './baseHandlers'
import {
mutableCollectionHandlers,
)
}
+// Return a reactive-copy of the original object, where only the root level
+// properties are reactive, and does NOT unwrap refs nor recursively convert
+// returned properties.
+export function shallowReactive<T extends object>(target: T): T {
+ return createReactiveObject(
+ target,
+ rawToReactive,
+ reactiveToRaw,
+ shallowReactiveHandlers,
+ mutableCollectionHandlers
+ )
+}
+
export function readonly<T extends object>(
target: T
): Readonly<UnwrapNestedRefs<T>> {
- // value is a mutable observable, retrieve its original and return
- // a readonly version.
- if (reactiveToRaw.has(target)) {
- target = reactiveToRaw.get(target)
- }
return createReactiveObject(
target,
rawToReadonly,
)
}
-// Return a reactive-copy of the original object, where only the root level
-// properties are reactive, and does NOT unwrap refs nor recursively convert
-// returned properties.
-export function shallowReactive<T extends object>(target: T): T {
- return createReactiveObject(
- target,
- rawToReactive,
- reactiveToRaw,
- shallowReactiveHandlers,
- mutableCollectionHandlers
- )
-}
-
function createReactiveObject(
target: unknown,
toProxy: WeakMap<any, any>,
}
export function toRaw<T>(observed: T): T {
- return reactiveToRaw.get(observed) || readonlyToRaw.get(observed) || observed
+ observed = readonlyToRaw.get(observed) || observed
+ return reactiveToRaw.get(observed) || observed
}
export function markNonReactive<T extends object>(value: T): T {
render,
getCurrentInstance,
nodeOps,
- createApp
+ createApp,
+ shallowReadonly
} from '@vue/runtime-test'
import { mockWarn } from '@vue/shared'
import { ComponentInternalInstance } from '../src/component'
}
render(h(Comp), nodeOps.createElement('div'))
expect(instanceProxy.$data).toBe(instance!.data)
- expect(instanceProxy.$props).toBe(instance!.props)
- expect(instanceProxy.$attrs).toBe(instance!.attrs)
- expect(instanceProxy.$slots).toBe(instance!.slots)
- expect(instanceProxy.$refs).toBe(instance!.refs)
+ expect(instanceProxy.$props).toBe(shallowReadonly(instance!.props))
+ expect(instanceProxy.$attrs).toBe(shallowReadonly(instance!.attrs))
+ expect(instanceProxy.$slots).toBe(shallowReadonly(instance!.slots))
+ expect(instanceProxy.$refs).toBe(shallowReadonly(instance!.refs))
expect(instanceProxy.$parent).toBe(
instance!.parent && instance!.parent.proxy
)
reactive,
ReactiveEffect,
pauseTracking,
- resetTracking
+ resetTracking,
+ shallowReadonly
} from '@vue/reactivity'
import {
ComponentPublicInstance,
setup,
instance,
ErrorCodes.SETUP_FUNCTION,
- [instance.props, setupContext]
+ [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
)
resetTracking()
currentInstance = null
}
}
-const slotsHandlers: ProxyHandler<InternalSlots> = {
- set: () => {
- warn(`setupContext.slots is readonly.`)
- return false
- },
- deleteProperty: () => {
- warn(`setupContext.slots is readonly.`)
- return false
- }
-}
-
const attrHandlers: ProxyHandler<Data> = {
get: (target, key: string) => {
markAttrsAccessed()
return new Proxy(instance.attrs, attrHandlers)
},
get slots() {
- return new Proxy(instance.slots, slotsHandlers)
+ return shallowReadonly(instance.slots)
},
get emit() {
return (event: string, ...args: any[]) => instance.emit(event, ...args)
-import { toRaw, lock, unlock, shallowReadonly } from '@vue/reactivity'
+import { toRaw, shallowReactive } from '@vue/reactivity'
import {
EMPTY_OBJ,
camelize,
if (isStateful) {
// stateful
- instance.props = isSSR ? props : shallowReadonly(props)
+ instance.props = isSSR ? props : shallowReactive(props)
} else {
if (!options) {
// functional w/ optional props, props === attrs
rawProps: Data | null,
optimized: boolean
) {
- // allow mutation of propsProxy (which is readonly by default)
- unlock()
-
const {
props,
attrs,
}
}
- // lock readonly
- lock()
-
if (__DEV__ && rawOptions && rawProps) {
validateProps(props, rawOptions)
}
import { nextTick, queueJob } from './scheduler'
import { instanceWatch } from './apiWatch'
import { EMPTY_OBJ, hasOwn, isGloballyWhitelisted, NOOP } from '@vue/shared'
-import { ReactiveEffect, UnwrapRef, toRaw } from '@vue/reactivity'
+import {
+ ReactiveEffect,
+ UnwrapRef,
+ toRaw,
+ shallowReadonly
+} from '@vue/reactivity'
import {
ExtractComputedReturns,
ComponentOptionsBase,
$: i => i,
$el: i => i.vnode.el,
$data: i => i.data,
- $props: i => i.props,
- $attrs: i => i.attrs,
- $slots: i => i.slots,
- $refs: i => i.refs,
+ $props: i => (__DEV__ ? shallowReadonly(i.props) : i.props),
+ $attrs: i => (__DEV__ ? shallowReadonly(i.attrs) : i.attrs),
+ $slots: i => (__DEV__ ? shallowReadonly(i.slots) : i.slots),
+ $refs: i => (__DEV__ ? shallowReadonly(i.refs) : i.refs),
$parent: i => i.parent && i.parent.proxy,
$root: i => i.root && i.root.proxy,
$emit: i => i.emit,
readonly,
isReadonly,
shallowReactive,
+ shallowReadonly,
markNonReactive,
toRaw
} from '@vue/reactivity'