expect(instanceProxy.$attrs).toBe(instance!.attrs)
expect(instanceProxy.$slots).toBe(instance!.slots)
expect(instanceProxy.$refs).toBe(instance!.refs)
- expect(instanceProxy.$parent).toBe(instance!.parent)
- expect(instanceProxy.$root).toBe(instance!.root)
+ expect(instanceProxy.$parent).toBe(
+ instance!.parent && instance!.parent.proxy
+ )
+ expect(instanceProxy.$root).toBe(instance!.root.proxy)
expect(instanceProxy.$emit).toBe(instance!.emit)
expect(instanceProxy.$el).toBe(instance!.vnode.el)
expect(instanceProxy.$options).toBe(instance!.type)
// set non-existent (goes into sink)
instanceProxy.baz = 1
expect('baz' in instanceProxy).toBe(true)
+
+ // dev mode ownKeys check for console inspection
+ expect(Object.keys(instanceProxy)).toMatchObject([
+ 'msg',
+ 'bar',
+ 'foo',
+ 'baz'
+ ])
})
// #864
resetTracking
} from '@vue/reactivity'
import {
- PublicInstanceProxyHandlers,
ComponentPublicInstance,
- runtimeCompiledRenderProxyHandlers
+ ComponentPublicProxyTarget,
+ PublicInstanceProxyHandlers,
+ RuntimeCompiledPublicInstanceProxyHandlers,
+ createDevProxyTarget,
+ exposePropsOnDevProxyTarget,
+ exposeRenderContextOnDevProxyTarget
} from './componentProxy'
import { ComponentPropsOptions, resolveProps } from './componentProps'
import { Slots, resolveSlots } from './componentSlots'
attrs: Data
slots: Slots
proxy: ComponentPublicInstance | null
+ proxyTarget: ComponentPublicProxyTarget
// alternative proxy used only for runtime-compiled render functions using
// `with` block
withProxy: ComponentPublicInstance | null
parent,
appContext,
type: vnode.type as Component,
- root: null!, // set later so it can point to itself
+ root: null!, // to be immediately set
next: null,
subTree: null!, // will be set synchronously right after creation
update: null!, // will be set synchronously right after creation
render: null,
proxy: null,
+ proxyTarget: null!, // to be immediately set
withProxy: null,
propsProxy: null,
setupContext: null,
ec: null,
emit: null as any // to be set immediately
}
+ if (__DEV__) {
+ instance.proxyTarget = createDevProxyTarget(instance)
+ } else {
+ instance.proxyTarget = { _: instance }
+ }
instance.root = parent ? parent.root : instance
instance.emit = emit.bind(null, instance)
return instance
// 0. create render proxy property access cache
instance.accessCache = {}
// 1. create public instance / render proxy
- instance.proxy = new Proxy(instance, PublicInstanceProxyHandlers)
+ instance.proxy = new Proxy(instance.proxyTarget, PublicInstanceProxyHandlers)
+ if (__DEV__) {
+ exposePropsOnDevProxyTarget(instance)
+ }
// 2. create props proxy
// the propsProxy is a reactive AND readonly proxy to the actual props.
// it will be updated in resolveProps() on updates before render
if (isSSR) {
// return the promise so server-renderer can wait on it
return setupResult.then((resolvedResult: unknown) => {
- handleSetupResult(instance, resolvedResult, parentSuspense, isSSR)
+ handleSetupResult(instance, resolvedResult, isSSR)
})
} else if (__FEATURE_SUSPENSE__) {
// async setup returned Promise.
)
}
} else {
- handleSetupResult(instance, setupResult, parentSuspense, isSSR)
+ handleSetupResult(instance, setupResult, isSSR)
}
} else {
finishComponentSetup(instance, isSSR)
export function handleSetupResult(
instance: ComponentInternalInstance,
setupResult: unknown,
- parentSuspense: SuspenseBoundary | null,
isSSR: boolean
) {
if (isFunction(setupResult)) {
// setup returned bindings.
// assuming a render function compiled from template is present.
instance.renderContext = reactive(setupResult)
+ if (__DEV__) {
+ exposeRenderContextOnDevProxyTarget(instance)
+ }
} else if (__DEV__ && setupResult !== undefined) {
warn(
`setup() should return an object. Received: ${
// also only allows a whitelist of globals to fallthrough.
if (instance.render._rc) {
instance.withProxy = new Proxy(
- instance,
- runtimeCompiledRenderProxyHandlers
+ instance.proxyTarget,
+ RuntimeCompiledPublicInstanceProxyHandlers
)
}
}
import {
reactive,
ComputedGetter,
- WritableComputedOptions
+ WritableComputedOptions,
+ ComputedRef
} from '@vue/reactivity'
-import { ComponentObjectPropsOptions, ExtractPropTypes } from './componentProps'
+import {
+ ComponentObjectPropsOptions,
+ ExtractPropTypes,
+ normalizePropsOptions
+} from './componentProps'
import { EmitsOptions } from './componentEmits'
import { Directive } from './directives'
import { ComponentPublicInstance } from './componentProxy'
options: ComponentOptions,
asMixin: boolean = false
) {
+ const proxyTarget = instance.proxyTarget
const ctx = instance.proxy!
const {
// composition
const globalMixins = instance.appContext.mixins
// call it only during dev
- const checkDuplicateProperties = __DEV__ ? createDuplicateChecker() : null
+
// applyOptions is called non-as-mixin once per instance
if (!asMixin) {
callSyncHook('beforeCreate', options, ctx, globalMixins)
applyMixins(instance, mixins)
}
+ const checkDuplicateProperties = __DEV__ ? createDuplicateChecker() : null
+
if (__DEV__ && propsOptions) {
- for (const key in propsOptions) {
+ for (const key in normalizePropsOptions(propsOptions)[0]) {
checkDuplicateProperties!(OptionTypes.PROPS, key)
}
}
if (__DEV__) {
for (const key in data) {
checkDuplicateProperties!(OptionTypes.DATA, key)
+ if (!(key in proxyTarget)) proxyTarget[key] = data[key]
}
}
instance.data = reactive(data)
if (computedOptions) {
for (const key in computedOptions) {
const opt = (computedOptions as ComputedOptions)[key]
-
- __DEV__ && checkDuplicateProperties!(OptionTypes.COMPUTED, key)
-
if (isFunction(opt)) {
renderContext[key] = computed(opt.bind(ctx, ctx))
} else {
warn(`Computed property "${key}" has no getter.`)
}
}
+ if (__DEV__) {
+ checkDuplicateProperties!(OptionTypes.COMPUTED, key)
+ if (renderContext[key] && !(key in proxyTarget)) {
+ Object.defineProperty(proxyTarget, key, {
+ enumerable: true,
+ get: () => (renderContext[key] as ComputedRef).value
+ })
+ }
+ }
}
}
for (const key in methods) {
const methodHandler = (methods as MethodOptions)[key]
if (isFunction(methodHandler)) {
- __DEV__ && checkDuplicateProperties!(OptionTypes.METHODS, key)
renderContext[key] = methodHandler.bind(ctx)
+ if (__DEV__) {
+ checkDuplicateProperties!(OptionTypes.METHODS, key)
+ if (!(key in proxyTarget)) {
+ proxyTarget[key] = renderContext[key]
+ }
+ }
} else if (__DEV__) {
warn(
`Method "${key}" has type "${typeof methodHandler}" in the component definition. ` +
if (isArray(injectOptions)) {
for (let i = 0; i < injectOptions.length; i++) {
const key = injectOptions[i]
- __DEV__ && checkDuplicateProperties!(OptionTypes.INJECT, key)
renderContext[key] = inject(key)
+ if (__DEV__) {
+ checkDuplicateProperties!(OptionTypes.INJECT, key)
+ proxyTarget[key] = renderContext[key]
+ }
}
} else {
for (const key in injectOptions) {
- __DEV__ && checkDuplicateProperties!(OptionTypes.INJECT, key)
const opt = injectOptions[key]
if (isObject(opt)) {
renderContext[key] = inject(opt.from, opt.default)
} else {
renderContext[key] = inject(opt)
}
+ if (__DEV__) {
+ checkDuplicateProperties!(OptionTypes.INJECT, key)
+ proxyTarget[key] = renderContext[key]
+ }
}
}
}
import { nextTick, queueJob } from './scheduler'
import { instanceWatch } from './apiWatch'
import { EMPTY_OBJ, hasOwn, isGloballyWhitelisted, NOOP } from '@vue/shared'
-import { ReactiveEffect, UnwrapRef } from '@vue/reactivity'
+import { ReactiveEffect, UnwrapRef, toRaw } from '@vue/reactivity'
import {
ExtractComputedReturns,
ComponentOptionsBase,
$attrs: i => i.attrs,
$slots: i => i.slots,
$refs: i => i.refs,
- $parent: i => i.parent,
- $root: i => i.root,
+ $parent: i => i.parent && i.parent.proxy,
+ $root: i => i.root && i.root.proxy,
$emit: i => i.emit,
$options: i => (__FEATURE_OPTIONS__ ? resolveMergedOptions(i) : i.type),
$forceUpdate: i => () => queueJob(i.update),
OTHER
}
+export interface ComponentPublicProxyTarget {
+ [key: string]: any
+ _: ComponentInternalInstance
+}
+
export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
- get(target: ComponentInternalInstance, key: string) {
+ get({ _: instance }: ComponentPublicProxyTarget, key: string) {
const {
renderContext,
data,
type,
sink,
appContext
- } = target
+ } = instance
// data / props / renderContext
// This getter gets called for every property access on the render context
if (__DEV__ && key === '$attrs') {
markAttrsAccessed()
}
- return publicGetter(target)
+ return publicGetter(instance)
} else if (hasOwn(sink, key)) {
return sink[key]
} else if (
}
},
- has(target: ComponentInternalInstance, key: string) {
- const { data, accessCache, renderContext, type, sink } = target
+ has(
+ {
+ _: { data, accessCache, renderContext, type, sink }
+ }: ComponentPublicProxyTarget,
+ key: string
+ ) {
return (
accessCache![key] !== undefined ||
(data !== EMPTY_OBJ && hasOwn(data, key)) ||
hasOwn(renderContext, key) ||
- (type.props && hasOwn(type.props, key)) ||
+ (type.props && hasOwn(normalizePropsOptions(type.props)[0], key)) ||
hasOwn(publicPropertiesMap, key) ||
hasOwn(sink, key)
)
},
- set(target: ComponentInternalInstance, key: string, value: any): boolean {
- const { data, renderContext } = target
+ set(
+ { _: instance }: ComponentPublicProxyTarget,
+ key: string,
+ value: any
+ ): boolean {
+ const { data, renderContext } = instance
if (data !== EMPTY_OBJ && hasOwn(data, key)) {
data[key] = value
} else if (hasOwn(renderContext, key)) {
renderContext[key] = value
- } else if (key[0] === '$' && key.slice(1) in target) {
+ } else if (key[0] === '$' && key.slice(1) in instance) {
__DEV__ &&
warn(
`Attempting to mutate public property "${key}". ` +
`Properties starting with $ are reserved and readonly.`,
- target
+ instance
)
return false
- } else if (key in target.props) {
+ } else if (key in instance.props) {
__DEV__ &&
- warn(`Attempting to mutate prop "${key}". Props are readonly.`, target)
+ warn(
+ `Attempting to mutate prop "${key}". Props are readonly.`,
+ instance
+ )
return false
} else {
- target.sink[key] = value
+ instance.sink[key] = value
+ if (__DEV__) {
+ instance.proxyTarget[key] = value
+ }
}
return true
}
}
-export const runtimeCompiledRenderProxyHandlers = {
+export const RuntimeCompiledPublicInstanceProxyHandlers = {
...PublicInstanceProxyHandlers,
- get(target: ComponentInternalInstance, key: string) {
+ get(target: ComponentPublicProxyTarget, key: string) {
// fast path for unscopables when using `with` block
if ((key as any) === Symbol.unscopables) {
return
}
return PublicInstanceProxyHandlers.get!(target, key, target)
},
- has(_target: ComponentInternalInstance, key: string) {
+ has(_: ComponentPublicProxyTarget, key: string) {
return key[0] !== '_' && !isGloballyWhitelisted(key)
}
}
+
+// In dev mode, the proxy target exposes the same properties as seen on `this`
+// for easier console inspection. In prod mode it will be an empty object so
+// these properties definitions can be skipped.
+export function createDevProxyTarget(instance: ComponentInternalInstance) {
+ const target: Record<string, any> = {}
+
+ // expose internal instance for proxy handlers
+ Object.defineProperty(target, `_`, {
+ get: () => instance
+ })
+
+ // expose public properties
+ Object.keys(publicPropertiesMap).forEach(key => {
+ Object.defineProperty(target, key, {
+ get: () => publicPropertiesMap[key](instance)
+ })
+ })
+
+ // expose global properties
+ const { globalProperties } = instance.appContext.config
+ Object.keys(globalProperties).forEach(key => {
+ Object.defineProperty(target, key, {
+ get: () => globalProperties[key]
+ })
+ })
+
+ return target as ComponentPublicProxyTarget
+}
+
+export function exposePropsOnDevProxyTarget(
+ instance: ComponentInternalInstance
+) {
+ const {
+ proxyTarget,
+ type: { props: propsOptions }
+ } = instance
+ if (propsOptions) {
+ Object.keys(normalizePropsOptions(propsOptions)[0]).forEach(key => {
+ Object.defineProperty(proxyTarget, key, {
+ enumerable: true,
+ get: () => instance.props[key],
+ // intercepted by the proxy so no need for implementation,
+ // but needed to prevent set errors
+ set: NOOP
+ })
+ })
+ }
+}
+
+export function exposeRenderContextOnDevProxyTarget(
+ instance: ComponentInternalInstance
+) {
+ const { proxyTarget, renderContext } = instance
+ Object.keys(toRaw(renderContext)).forEach(key => {
+ Object.defineProperty(proxyTarget, key, {
+ enumerable: true,
+ get: () => renderContext[key],
+ // intercepted by the proxy so no need for implementation,
+ // but needed to prevent set errors
+ set: NOOP
+ })
+ })
+}
if (__DEV__) {
pushWarningContext(vnode)
}
- handleSetupResult(instance, asyncSetupResult, suspense, false)
+ handleSetupResult(instance, asyncSetupResult, false)
if (hydratedEl) {
// vnode may have been replaced if an update happened before the
// async dep is reoslved.