-import { reactive, readonly, toRaw } from './reactive'
+import { reactive, readonly, toRaw, ReactiveFlags } from './reactive'
import { TrackOpTypes, TriggerOpTypes } from './operations'
import { track, trigger, ITERATE_KEY } from './effect'
import { isObject, hasOwn, isSymbol, hasChanged, isArray } from '@vue/shared'
function createGetter(isReadonly = false, shallow = false) {
return function get(target: object, key: string | symbol, receiver: object) {
+ if (key === ReactiveFlags.isReactive) {
+ return !isReadonly
+ } else if (key === ReactiveFlags.isReadonly) {
+ return isReadonly
+ } else if (key === ReactiveFlags.raw) {
+ return target
+ }
+
const targetIsArray = isArray(target)
if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
-import { toRaw, reactive, readonly } from './reactive'
+import { toRaw, reactive, readonly, ReactiveFlags } from './reactive'
import { track, trigger, ITERATE_KEY, MAP_KEY_ITERATE_KEY } from './effect'
import { TrackOpTypes, TriggerOpTypes } from './operations'
import {
)
})
-function createInstrumentationGetter(
- instrumentations: Record<string, Function>
-) {
+function createInstrumentationGetter(isReadonly: boolean) {
+ const instrumentations = isReadonly
+ ? readonlyInstrumentations
+ : mutableInstrumentations
+
return (
target: CollectionTypes,
key: string | symbol,
receiver: CollectionTypes
- ) =>
- Reflect.get(
+ ) => {
+ if (key === ReactiveFlags.isReactive) {
+ return !isReadonly
+ } else if (key === ReactiveFlags.isReadonly) {
+ return isReadonly
+ } else if (key === ReactiveFlags.raw) {
+ return target
+ }
+
+ return Reflect.get(
hasOwn(instrumentations, key) && key in target
? instrumentations
: target,
key,
receiver
)
+ }
}
export const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {
- get: createInstrumentationGetter(mutableInstrumentations)
+ get: createInstrumentationGetter(false)
}
export const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {
- get: createInstrumentationGetter(readonlyInstrumentations)
+ get: createInstrumentationGetter(true)
}
function checkIdentityKeys(
}
})
computed = {
- _isRef: true,
+ __v_isRef: true,
// expose effect so computed can be stopped
effect: runner,
get value() {
shallowReactive,
shallowReadonly,
markRaw,
- toRaw
+ toRaw,
+ ReactiveFlags
} from './reactive'
export {
computed,
-import { isObject, toRawType } from '@vue/shared'
+import { isObject, toRawType, def } from '@vue/shared'
import {
mutableHandlers,
readonlyHandlers,
import { makeMap } from '@vue/shared'
// WeakMaps that store {raw <-> observed} pairs.
-const rawToReactive = new WeakMap<any, any>()
-const reactiveToRaw = new WeakMap<any, any>()
-const rawToReadonly = new WeakMap<any, any>()
-const readonlyToRaw = new WeakMap<any, any>()
+// const rawToReactive = new WeakMap<any, any>()
+// const reactiveToRaw = new WeakMap<any, any>()
+// const rawToReadonly = new WeakMap<any, any>()
+// const readonlyToRaw = new WeakMap<any, any>()
-// WeakSets for values that are marked readonly or non-reactive during
-// observable creation.
-const rawValues = new WeakSet<any>()
+export const enum ReactiveFlags {
+ skip = '__v_skip',
+ isReactive = '__v_isReactive',
+ isReadonly = '__v_isReadonly',
+ raw = '__v_raw',
+ reactive = '__v_reactive',
+ readonly = '__v_readonly'
+}
+
+interface Target {
+ __v_skip?: boolean
+ __v_isReactive?: boolean
+ __v_isReadonly?: boolean
+ __v_raw?: any
+ __v_reactive?: any
+ __v_readonly?: any
+}
const collectionTypes = new Set<Function>([Set, Map, WeakMap, WeakSet])
const isObservableType = /*#__PURE__*/ makeMap(
'Object,Array,Map,Set,WeakMap,WeakSet'
)
-const canObserve = (value: any): boolean => {
+const canObserve = (value: Target): boolean => {
return (
- !value._isVNode &&
+ !value.__v_skip &&
isObservableType(toRawType(value)) &&
- !rawValues.has(value) &&
!Object.isFrozen(value)
)
}
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
- if (readonlyToRaw.has(target)) {
+ if (target && (target as Target).__v_isReadonly) {
return target
}
return createReactiveObject(
target,
- rawToReactive,
- reactiveToRaw,
+ false,
mutableHandlers,
mutableCollectionHandlers
)
export function shallowReactive<T extends object>(target: T): T {
return createReactiveObject(
target,
- rawToReactive,
- reactiveToRaw,
+ false,
shallowReactiveHandlers,
mutableCollectionHandlers
)
): Readonly<UnwrapNestedRefs<T>> {
return createReactiveObject(
target,
- rawToReadonly,
- readonlyToRaw,
+ true,
readonlyHandlers,
readonlyCollectionHandlers
)
): Readonly<{ [K in keyof T]: UnwrapNestedRefs<T[K]> }> {
return createReactiveObject(
target,
- rawToReadonly,
- readonlyToRaw,
+ true,
shallowReadonlyHandlers,
readonlyCollectionHandlers
)
}
function createReactiveObject(
- target: unknown,
- toProxy: WeakMap<any, any>,
- toRaw: WeakMap<any, any>,
+ target: Target,
+ isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>
) {
}
return target
}
+ // target is already a Proxy, return it.
+ // excpetion: calling readonly() on a reactive object
+ if (target.__v_raw && !(isReadonly && target.__v_isReactive)) {
+ return target
+ }
// target already has corresponding Proxy
- let observed = toProxy.get(target)
+ let observed = isReadonly ? target.__v_readonly : target.__v_reactive
if (observed !== void 0) {
return observed
}
- // target is already a Proxy
- if (toRaw.has(target)) {
- return target
- }
// only a whitelist of value types can be observed.
if (!canObserve(target)) {
return target
? collectionHandlers
: baseHandlers
observed = new Proxy(target, handlers)
- toProxy.set(target, observed)
- toRaw.set(observed, target)
+ def(
+ target,
+ isReadonly ? ReactiveFlags.readonly : ReactiveFlags.reactive,
+ observed
+ )
return observed
}
export function isReactive(value: unknown): boolean {
- value = readonlyToRaw.get(value) || value
- return reactiveToRaw.has(value)
+ if (isReadonly(value)) {
+ return isReactive((value as Target).__v_raw)
+ }
+ return !!(value && (value as Target).__v_isReactive)
}
export function isReadonly(value: unknown): boolean {
- return readonlyToRaw.has(value)
+ return !!(value && (value as Target).__v_isReadonly)
}
export function isProxy(value: unknown): boolean {
- return readonlyToRaw.has(value) || reactiveToRaw.has(value)
+ return isReactive(value) || isReadonly(value)
}
export function toRaw<T>(observed: T): T {
- observed = readonlyToRaw.get(observed) || observed
- return reactiveToRaw.get(observed) || observed
+ return (observed && toRaw((observed as Target).__v_raw)) || observed
}
export function markRaw<T extends object>(value: T): T {
- rawValues.add(value)
+ def(value, ReactiveFlags.skip, true)
return value
}
import { ComputedRef } from './computed'
import { CollectionTypes } from './collectionHandlers'
-const isRefSymbol = Symbol()
-
export interface Ref<T = any> {
- // This field is necessary to allow TS to differentiate a Ref from a plain
- // object that happens to have a "value" field.
- // However, checking a symbol on an arbitrary object is much slower than
- // checking a plain property, so we use a _isRef plain property for isRef()
- // check in the actual implementation.
- // The reason for not just declaring _isRef in the interface is because we
- // don't want this internal field to leak into userland autocompletion -
- // a private symbol, on the other hand, achieves just that.
- [isRefSymbol]: true
+ /**
+ * @internal
+ */
+ __v_isRef: true
value: T
}
export function isRef<T>(r: Ref<T> | unknown): r is Ref<T>
export function isRef(r: any): r is Ref {
- return r ? r._isRef === true : false
+ return r ? r.__v_isRef === true : false
}
export function ref<T extends object>(
}
let value = shallow ? rawValue : convert(rawValue)
const r = {
- _isRef: true,
+ __v_isRef: true,
get value() {
track(r, TrackOpTypes.GET, 'value')
return value
() => trigger(r, TriggerOpTypes.SET, 'value')
)
const r = {
- _isRef: true,
+ __v_isRef: true,
get value() {
return get()
},
key: K
): Ref<T[K]> {
return {
- _isRef: true,
+ __v_isRef: true,
get value(): any {
return object[key]
},
--- /dev/null
+import { render, h, nodeOps, reactive, isReactive } from '@vue/runtime-test'
+
+describe('misc', () => {
+ test('component public instance should not be observable', () => {
+ let instance: any
+ const Comp = {
+ render() {},
+ mounted() {
+ instance = this
+ }
+ }
+ render(h(Comp), nodeOps.createElement('div'))
+ expect(instance).toBeDefined()
+ const r = reactive(instance)
+ expect(r).toBe(instance)
+ expect(isReactive(r)).toBe(false)
+ })
+})
} from '../src/vnode'
import { Data } from '../src/component'
import { ShapeFlags, PatchFlags } from '@vue/shared'
-import { h } from '../src'
+import { h, reactive, isReactive } from '../src'
import { createApp, nodeOps, serializeInner } from '@vue/runtime-test'
describe('vnode', () => {
createApp(App).mount(root)
expect(serializeInner(root)).toBe('<h1>Root Component</h1>')
})
+
+ test('should not be observable', () => {
+ const a = createVNode('div')
+ const b = reactive(a)
+ expect(b).toBe(a)
+ expect(isReactive(b)).toBe(false)
+ })
})
})
ReactiveEffect,
pauseTracking,
resetTracking,
- shallowReadonly,
- markRaw
+ shallowReadonly
} from '@vue/reactivity'
import {
ComponentPublicInstance,
instance.accessCache = {}
// 1. create public instance / render proxy
// also mark it raw so it's never observed
- instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers))
+ instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers)
if (__DEV__) {
exposePropsOnRenderContext(instance)
}
ReactiveEffect,
UnwrapRef,
toRaw,
- shallowReadonly
+ shallowReadonly,
+ ReactiveFlags
} from '@vue/reactivity'
import {
ExtractComputedReturns,
appContext
} = instance
+ // let @vue/reatvitiy know it should never observe Vue public instances.
+ if (key === ReactiveFlags.skip) {
+ return true
+ }
+
// data / props / ctx
// This getter gets called for every property access on the render context
// during render and is a major hotspot. The most expensive part of this
} else if (
__DEV__ &&
currentRenderingInstance &&
- // #1091 avoid isRef/isVNode checks on component instance leading to
- // infinite warning loop
- key !== '_isRef' &&
- key !== '_isVNode'
+ // #1091 avoid internal isRef/isVNode checks on component instance leading
+ // to infinite warning loop
+ key.indexOf('__v') !== 0
) {
if (data !== EMPTY_OBJ && key[0] === '$' && hasOwn(data, key)) {
warn(
type RawProps = VNodeProps & {
// used to differ from a single VNode object as children
- _isVNode?: never
+ __v_isVNode?: never
// used to differ from Array children
[Symbol.iterator]?: never
}
| null
export interface VNode<HostNode = RendererNode, HostElement = RendererElement> {
- _isVNode: true
+ /**
+ * @internal
+ */
+ __v_isVNode: true
+ /**
+ * @internal
+ */
+ __v_skip: true
type: VNodeTypes
props: VNodeProps | null
key: string | number | null
}
export function isVNode(value: any): value is VNode {
- return value ? value._isVNode === true : false
+ return value ? value.__v_isVNode === true : false
}
export function isSameVNodeType(n1: VNode, n2: VNode): boolean {
}
const vnode: VNode = {
- _isVNode: true,
+ __v_isVNode: true,
+ __v_skip: true,
type,
props,
key: props && normalizeKey(props),
// This is intentionally NOT using spread or extend to avoid the runtime
// key enumeration cost.
return {
- _isVNode: true,
+ __v_isVNode: true,
+ __v_skip: true,
type: vnode.type,
props,
key: props && normalizeKey(props),
}
export const def = (obj: object, key: string | symbol, value: any) => {
- Object.defineProperty(obj, key, { value })
+ Object.defineProperty(obj, key, {
+ configurable: true,
+ value
+ })
}