render(h(Comp), nodeOps.createElement('div'))
instanceProxy.foo = 1
expect(instanceProxy.foo).toBe(1)
- expect(instance!.proxyTarget.foo).toBe(1)
+ expect(instance!.ctx.foo).toBe(1)
})
test('globalProperties', () => {
// set should overwrite globalProperties with local
instanceProxy.foo = 2
// expect(instanceProxy.foo).toBe(2)
- expect(instance!.proxyTarget.foo).toBe(2)
+ expect(instance!.ctx.foo).toBe(2)
// should not affect global
expect(app.config.globalProperties.foo).toBe(1)
})
expect('msg' in instanceProxy).toBe(true)
// data
expect('foo' in instanceProxy).toBe(true)
- // renderContext
+ // ctx
expect('bar' in instanceProxy).toBe(true)
// public properties
expect('$el' in instanceProxy).toBe(true)
} from '@vue/reactivity'
import {
ComponentPublicInstance,
- ComponentPublicProxyTarget,
PublicInstanceProxyHandlers,
RuntimeCompiledPublicInstanceProxyHandlers,
- createDevProxyTarget,
- exposePropsOnDevProxyTarget,
- exposeSetupStateOnDevProxyTarget
+ createRenderContext,
+ exposePropsOnRenderContext,
+ exposeSetupStateOnRenderContext
} from './componentProxy'
import { ComponentPropsOptions, initProps } from './componentProps'
import { Slots, initSlots, InternalSlots } from './componentSlots'
components: Record<string, Component>
directives: Record<string, Directive>
- // the rest are only for stateful components
- renderContext: Data
+ // the rest are only for stateful components ---------------------------------
+
+ // main proxy that serves as the public instance (`this`)
+ proxy: ComponentPublicInstance | null
+ // alternative proxy used only for runtime-compiled render functions using
+ // `with` block
+ withProxy: ComponentPublicInstance | null
+ // This is the target for the public instance proxy. It also holds properties
+ // injected by user options (computed, methods etc.) and user-attached
+ // custom properties (via `this.x = ...`)
+ ctx: Data
+
+ // internal state
data: Data
props: Data
attrs: Data
slots: InternalSlots
- proxy: ComponentPublicInstance | null
refs: Data
emit: EmitFn
setupState: Data
setupContext: SetupContext | null
- // The target object for the public instance proxy. In dev mode, we also
- // define getters for all known instance properties on it so it can be
- // properly inspected in the console. These getters are skipped in prod mode
- // for performance. In addition, any user attached properties
- // (via `this.x = ...`) are also stored on this object.
- proxyTarget: ComponentPublicProxyTarget
- // alternative proxy used only for runtime-compiled render functions using
- // `with` block
- withProxy: ComponentPublicInstance | null
-
// suspense related
suspense: SuspenseBoundary | null
asyncDep: Promise<any> | null
update: null!, // will be set synchronously right after creation
render: null,
proxy: null,
- proxyTarget: null!, // to be immediately set
withProxy: null,
effects: null,
provides: parent ? parent.provides : Object.create(appContext.provides),
renderCache: [],
// state
- renderContext: EMPTY_OBJ,
+ ctx: EMPTY_OBJ,
data: EMPTY_OBJ,
props: EMPTY_OBJ,
attrs: EMPTY_OBJ,
emit: null as any // to be set immediately
}
if (__DEV__) {
- instance.proxyTarget = createDevProxyTarget(instance)
+ instance.ctx = createRenderContext(instance)
} else {
- instance.proxyTarget = { _: instance }
+ instance.ctx = { _: instance }
}
instance.root = parent ? parent.root : instance
instance.emit = emit.bind(null, instance)
// 0. create render proxy property access cache
instance.accessCache = {}
// 1. create public instance / render proxy
- instance.proxy = new Proxy(instance.proxyTarget, PublicInstanceProxyHandlers)
+ instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers)
if (__DEV__) {
- exposePropsOnDevProxyTarget(instance)
+ exposePropsOnRenderContext(instance)
}
// 2. call setup()
const { setup } = Component
// assuming a render function compiled from template is present.
instance.setupState = reactive(setupResult)
if (__DEV__) {
- exposeSetupStateOnDevProxyTarget(instance)
+ exposeSetupStateOnRenderContext(instance)
}
} else if (__DEV__ && setupResult !== undefined) {
warn(
// also only allows a whitelist of globals to fallthrough.
if (instance.render._rc) {
instance.withProxy = new Proxy(
- instance.proxyTarget,
+ instance.ctx,
RuntimeCompiledPublicInstanceProxyHandlers
)
}
import {
reactive,
ComputedGetter,
- WritableComputedOptions,
- ComputedRef,
- toRaw
+ WritableComputedOptions
} from '@vue/reactivity'
import {
ComponentObjectPropsOptions,
options: ComponentOptions,
asMixin: boolean = false
) {
- const proxyTarget = instance.proxyTarget
- const ctx = instance.proxy!
+ const publicThis = instance.proxy!
const {
// composition
mixins,
errorCaptured
} = options
- const renderContext = toRaw(
- instance.renderContext === EMPTY_OBJ &&
- (computedOptions || methods || watchOptions || injectOptions)
- ? (instance.renderContext = reactive({}))
- : instance.renderContext
- )
-
+ const ctx = instance.ctx
const globalMixins = instance.appContext.mixins
// call it only during dev
// applyOptions is called non-as-mixin once per instance
if (!asMixin) {
- callSyncHook('beforeCreate', options, ctx, globalMixins)
+ callSyncHook('beforeCreate', options, publicThis, globalMixins)
// global mixins are applied first
applyMixins(instance, globalMixins)
}
`Plain object usage is no longer supported.`
)
}
- const data = dataOptions.call(ctx, ctx)
+ const data = dataOptions.call(publicThis, publicThis)
if (__DEV__ && isPromise(data)) {
warn(
`data() returned a Promise - note data() cannot be async; If you ` +
if (__DEV__) {
for (const key in data) {
checkDuplicateProperties!(OptionTypes.DATA, key)
- if (!(key in proxyTarget)) proxyTarget[key] = data[key]
+ // expose data on ctx during dev
+ Object.defineProperty(ctx, key, {
+ configurable: true,
+ enumerable: true,
+ get: () => data[key],
+ set: NOOP
+ })
}
}
instance.data = reactive(data)
if (computedOptions) {
for (const key in computedOptions) {
const opt = (computedOptions as ComputedOptions)[key]
- if (isFunction(opt)) {
- renderContext[key] = computed(opt.bind(ctx, ctx))
- } else {
- const { get, set } = opt
- if (isFunction(get)) {
- renderContext[key] = computed({
- get: get.bind(ctx, ctx),
- set: isFunction(set)
- ? set.bind(ctx)
- : __DEV__
- ? () => {
- warn(
- `Write operation failed: computed property "${key}" is readonly.`
- )
- }
- : NOOP
- })
- } else if (__DEV__) {
- warn(`Computed property "${key}" has no getter.`)
- }
+ const get = isFunction(opt)
+ ? opt.bind(publicThis, publicThis)
+ : isFunction(opt.get)
+ ? opt.get.bind(publicThis, publicThis)
+ : NOOP
+ if (__DEV__ && get === NOOP) {
+ warn(`Computed property "${key}" has no getter.`)
}
+ const set =
+ !isFunction(opt) && isFunction(opt.set)
+ ? opt.set.bind(publicThis)
+ : __DEV__
+ ? () => {
+ warn(
+ `Write operation failed: computed property "${key}" is readonly.`
+ )
+ }
+ : NOOP
+ const c = computed({
+ get,
+ set
+ })
+ Object.defineProperty(ctx, key, {
+ enumerable: true,
+ configurable: true,
+ get: () => c.value,
+ set: v => (c.value = v)
+ })
if (__DEV__) {
checkDuplicateProperties!(OptionTypes.COMPUTED, key)
- if (renderContext[key] && !(key in proxyTarget)) {
- Object.defineProperty(proxyTarget, key, {
- enumerable: true,
- configurable: true,
- get: () => (renderContext[key] as ComputedRef).value,
- set: NOOP
- })
- }
}
}
}
for (const key in methods) {
const methodHandler = (methods as MethodOptions)[key]
if (isFunction(methodHandler)) {
- renderContext[key] = methodHandler.bind(ctx)
+ ctx[key] = methodHandler.bind(publicThis)
if (__DEV__) {
checkDuplicateProperties!(OptionTypes.METHODS, key)
- if (!(key in proxyTarget)) {
- proxyTarget[key] = renderContext[key]
- }
}
} else if (__DEV__) {
warn(
if (watchOptions) {
for (const key in watchOptions) {
- createWatcher(watchOptions[key], renderContext, ctx, key)
+ createWatcher(watchOptions[key], ctx, publicThis, key)
}
}
if (provideOptions) {
const provides = isFunction(provideOptions)
- ? provideOptions.call(ctx)
+ ? provideOptions.call(publicThis)
: provideOptions
for (const key in provides) {
provide(key, provides[key])
if (isArray(injectOptions)) {
for (let i = 0; i < injectOptions.length; i++) {
const key = injectOptions[i]
- renderContext[key] = inject(key)
+ ctx[key] = inject(key)
if (__DEV__) {
checkDuplicateProperties!(OptionTypes.INJECT, key)
- proxyTarget[key] = renderContext[key]
}
}
} else {
for (const key in injectOptions) {
const opt = injectOptions[key]
if (isObject(opt)) {
- renderContext[key] = inject(opt.from, opt.default)
+ ctx[key] = inject(opt.from, opt.default)
} else {
- renderContext[key] = inject(opt)
+ ctx[key] = inject(opt)
}
if (__DEV__) {
checkDuplicateProperties!(OptionTypes.INJECT, key)
- proxyTarget[key] = renderContext[key]
}
}
}
// lifecycle options
if (!asMixin) {
- callSyncHook('created', options, ctx, globalMixins)
+ callSyncHook('created', options, publicThis, globalMixins)
}
if (beforeMount) {
- onBeforeMount(beforeMount.bind(ctx))
+ onBeforeMount(beforeMount.bind(publicThis))
}
if (mounted) {
- onMounted(mounted.bind(ctx))
+ onMounted(mounted.bind(publicThis))
}
if (beforeUpdate) {
- onBeforeUpdate(beforeUpdate.bind(ctx))
+ onBeforeUpdate(beforeUpdate.bind(publicThis))
}
if (updated) {
- onUpdated(updated.bind(ctx))
+ onUpdated(updated.bind(publicThis))
}
if (activated) {
- onActivated(activated.bind(ctx))
+ onActivated(activated.bind(publicThis))
}
if (deactivated) {
- onDeactivated(deactivated.bind(ctx))
+ onDeactivated(deactivated.bind(publicThis))
}
if (errorCaptured) {
- onErrorCaptured(errorCaptured.bind(ctx))
+ onErrorCaptured(errorCaptured.bind(publicThis))
}
if (renderTracked) {
- onRenderTracked(renderTracked.bind(ctx))
+ onRenderTracked(renderTracked.bind(publicThis))
}
if (renderTriggered) {
- onRenderTriggered(renderTriggered.bind(ctx))
+ onRenderTriggered(renderTriggered.bind(publicThis))
}
if (beforeUnmount) {
- onBeforeUnmount(beforeUnmount.bind(ctx))
+ onBeforeUnmount(beforeUnmount.bind(publicThis))
}
if (unmounted) {
- onUnmounted(unmounted.bind(ctx))
+ onUnmounted(unmounted.bind(publicThis))
}
}
function createWatcher(
raw: ComponentWatchOptionItem,
- renderContext: Data,
- ctx: ComponentPublicInstance,
+ ctx: Data,
+ publicThis: ComponentPublicInstance,
key: string
) {
- const getter = () => (ctx as Data)[key]
+ const getter = () => (publicThis as Data)[key]
if (isString(raw)) {
- const handler = renderContext[raw]
+ const handler = ctx[raw]
if (isFunction(handler)) {
watch(getter, handler as WatchCallback)
} else if (__DEV__) {
warn(`Invalid watch handler specified by key "${raw}"`, handler)
}
} else if (isFunction(raw)) {
- watch(getter, raw.bind(ctx))
+ watch(getter, raw.bind(publicThis))
} else if (isObject(raw)) {
if (isArray(raw)) {
- raw.forEach(r => createWatcher(r, renderContext, ctx, key))
+ raw.forEach(r => createWatcher(r, ctx, publicThis, key))
} else {
- watch(getter, raw.handler.bind(ctx), raw)
+ watch(getter, raw.handler.bind(publicThis), raw)
}
} else if (__DEV__) {
warn(`Invalid watch option: "${key}"`)
OTHER
}
-export interface ComponentPublicProxyTarget {
+export interface ComponentRenderContext {
[key: string]: any
_: ComponentInternalInstance
}
export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
- get({ _: instance }: ComponentPublicProxyTarget, key: string) {
+ get({ _: instance }: ComponentRenderContext, key: string) {
const {
- renderContext,
+ ctx,
setupState,
data,
props,
accessCache,
type,
- proxyTarget,
appContext
} = instance
- // data / props / renderContext
+ // 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
// is the multiple hasOwn() calls. It's much faster to do a simple property
case AccessTypes.DATA:
return data[key]
case AccessTypes.CONTEXT:
- return renderContext[key]
+ return ctx[key]
case AccessTypes.PROPS:
return props![key]
// default: just fallthrough
) {
accessCache![key] = AccessTypes.PROPS
return props![key]
- } else if (renderContext !== EMPTY_OBJ && hasOwn(renderContext, key)) {
+ } else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {
accessCache![key] = AccessTypes.CONTEXT
- return renderContext[key]
+ return ctx[key]
} else {
accessCache![key] = AccessTypes.OTHER
}
}
- // public $xxx properties &
- // user-attached properties (falls through to proxyTarget)
const publicGetter = publicPropertiesMap[key]
let cssModule, globalProperties
+ // public $xxx properties
if (publicGetter) {
if (__DEV__ && key === '$attrs') {
markAttrsAccessed()
}
return publicGetter(instance)
- } else if (hasOwn(proxyTarget, key)) {
- return proxyTarget[key]
} else if (
+ // css module (injected by vue-loader)
(cssModule = type.__cssModules) &&
(cssModule = cssModule[key])
) {
return cssModule
} else if (
+ // global properties
((globalProperties = appContext.config.globalProperties),
hasOwn(globalProperties, key))
) {
},
set(
- { _: instance }: ComponentPublicProxyTarget,
+ { _: instance }: ComponentRenderContext,
key: string,
value: any
): boolean {
- const { data, setupState, renderContext } = instance
+ const { data, setupState, ctx } = instance
if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
setupState[key] = value
} else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
instance
)
return false
- } else if (hasOwn(renderContext, key)) {
- renderContext[key] = value
- } else if (key[0] === '$' && key.slice(1) in instance) {
+ }
+ if (key[0] === '$' && key.slice(1) in instance) {
__DEV__ &&
warn(
`Attempting to mutate public property "${key}". ` +
return false
} else {
if (__DEV__ && key in instance.appContext.config.globalProperties) {
- Object.defineProperty(instance.proxyTarget, key, {
- configurable: true,
+ Object.defineProperty(ctx, key, {
enumerable: true,
+ configurable: true,
value
})
} else {
- instance.proxyTarget[key] = value
+ ctx[key] = value
}
}
return true
has(
{
- _: {
- data,
- setupState,
- accessCache,
- renderContext,
- type,
- proxyTarget,
- appContext
- }
- }: ComponentPublicProxyTarget,
+ _: { data, setupState, accessCache, ctx, type, appContext }
+ }: ComponentRenderContext,
key: string
) {
return (
(data !== EMPTY_OBJ && hasOwn(data, key)) ||
(setupState !== EMPTY_OBJ && hasOwn(setupState, key)) ||
(type.props && hasOwn(normalizePropsOptions(type.props)[0]!, key)) ||
- hasOwn(renderContext, key) ||
+ hasOwn(ctx, key) ||
hasOwn(publicPropertiesMap, key) ||
- hasOwn(proxyTarget, key) ||
hasOwn(appContext.config.globalProperties, key)
)
}
}
if (__DEV__ && !__TEST__) {
- PublicInstanceProxyHandlers.ownKeys = (
- target: ComponentPublicProxyTarget
- ) => {
+ PublicInstanceProxyHandlers.ownKeys = (target: ComponentRenderContext) => {
warn(
`Avoid app logic that relies on enumerating keys on a component instance. ` +
`The keys will be empty in production mode to avoid performance overhead.`
export const RuntimeCompiledPublicInstanceProxyHandlers = {
...PublicInstanceProxyHandlers,
- get(target: ComponentPublicProxyTarget, key: string) {
+ get(target: ComponentRenderContext, key: string) {
// fast path for unscopables when using `with` block
if ((key as any) === Symbol.unscopables) {
return
}
return PublicInstanceProxyHandlers.get!(target, key, target)
},
- has(_: ComponentPublicProxyTarget, key: string) {
+ has(_: ComponentRenderContext, 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) {
+export function createRenderContext(instance: ComponentInternalInstance) {
const target: Record<string, any> = {}
// expose internal instance for proxy handlers
})
})
- return target as ComponentPublicProxyTarget
+ return target as ComponentRenderContext
}
-export function exposePropsOnDevProxyTarget(
+// dev only
+export function exposePropsOnRenderContext(
instance: ComponentInternalInstance
) {
const {
- proxyTarget,
+ ctx,
type: { props: propsOptions }
} = instance
if (propsOptions) {
Object.keys(normalizePropsOptions(propsOptions)[0]!).forEach(key => {
- Object.defineProperty(proxyTarget, key, {
+ Object.defineProperty(ctx, key, {
enumerable: true,
configurable: true,
get: () => instance.props[key],
}
}
-export function exposeSetupStateOnDevProxyTarget(
+// dev only
+export function exposeSetupStateOnRenderContext(
instance: ComponentInternalInstance
) {
- const { proxyTarget, setupState } = instance
+ const { ctx, setupState } = instance
Object.keys(toRaw(setupState)).forEach(key => {
- Object.defineProperty(proxyTarget, key, {
+ Object.defineProperty(ctx, key, {
enumerable: true,
configurable: true,
get: () => setupState[key],
RendererNode
} from '../renderer'
import { setTransitionHooks } from './BaseTransition'
-import { ComponentPublicProxyTarget } from '../componentProxy'
+import { ComponentRenderContext } from '../componentProxy'
type MatchPattern = string | RegExp | string[] | RegExp[]
type Cache = Map<CacheKey, VNode>
type Keys = Set<CacheKey>
-export interface KeepAliveContext extends ComponentPublicProxyTarget {
+export interface KeepAliveContext extends ComponentRenderContext {
renderer: RendererInternals
activate: (
vnode: VNode,
const instance = getCurrentInstance()!
const parentSuspense = instance.suspense
- // KeepAlive communicates with the instantiated renderer via the proxyTarget
- // as a shared context where the renderer passes in its internals,
+ // KeepAlive communicates with the instantiated renderer via the
+ // ctx where the renderer passes in its internals,
// and the KeepAlive instance exposes activate/deactivate implementations.
// The whole point of this is to avoid importing KeepAlive directly in the
// renderer to facilitate tree-shaking.
- const sharedContext = instance.proxyTarget as KeepAliveContext
+ const sharedContext = instance.ctx as KeepAliveContext
const {
renderer: {
p: patch,
) => {
if (n1 == null) {
if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
- ;(parentComponent!.proxyTarget as KeepAliveContext).activate(
+ ;(parentComponent!.ctx as KeepAliveContext).activate(
n2,
container,
anchor,
// inject renderer internals for keepAlive
if (isKeepAlive(initialVNode)) {
- ;(instance.proxyTarget as KeepAliveContext).renderer = internals
+ ;(instance.ctx as KeepAliveContext).renderer = internals
}
// resolve props and slots for setup context
if (shapeFlag & ShapeFlags.COMPONENT) {
if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
- ;(parentComponent!.proxyTarget as KeepAliveContext).deactivate(vnode)
+ ;(parentComponent!.ctx as KeepAliveContext).deactivate(vnode)
} else {
unmountComponent(vnode.component!, parentSuspense, doRemove)
}