refactor(runtime-core): refactor instance public proxy context object

This commit is contained in:
Evan You 2020-04-16 12:49:50 -04:00
parent b2662a62c5
commit 370fc820cc
6 changed files with 131 additions and 154 deletions

View File

@ -116,7 +116,7 @@ describe('component: proxy', () => {
render(h(Comp), nodeOps.createElement('div')) render(h(Comp), nodeOps.createElement('div'))
instanceProxy.foo = 1 instanceProxy.foo = 1
expect(instanceProxy.foo).toBe(1) expect(instanceProxy.foo).toBe(1)
expect(instance!.proxyTarget.foo).toBe(1) expect(instance!.ctx.foo).toBe(1)
}) })
test('globalProperties', () => { test('globalProperties', () => {
@ -141,7 +141,7 @@ describe('component: proxy', () => {
// set should overwrite globalProperties with local // set should overwrite globalProperties with local
instanceProxy.foo = 2 instanceProxy.foo = 2
// expect(instanceProxy.foo).toBe(2) // expect(instanceProxy.foo).toBe(2)
expect(instance!.proxyTarget.foo).toBe(2) expect(instance!.ctx.foo).toBe(2)
// should not affect global // should not affect global
expect(app.config.globalProperties.foo).toBe(1) expect(app.config.globalProperties.foo).toBe(1)
}) })
@ -177,7 +177,7 @@ describe('component: proxy', () => {
expect('msg' in instanceProxy).toBe(true) expect('msg' in instanceProxy).toBe(true)
// data // data
expect('foo' in instanceProxy).toBe(true) expect('foo' in instanceProxy).toBe(true)
// renderContext // ctx
expect('bar' in instanceProxy).toBe(true) expect('bar' in instanceProxy).toBe(true)
// public properties // public properties
expect('$el' in instanceProxy).toBe(true) expect('$el' in instanceProxy).toBe(true)

View File

@ -8,12 +8,11 @@ import {
} from '@vue/reactivity' } from '@vue/reactivity'
import { import {
ComponentPublicInstance, ComponentPublicInstance,
ComponentPublicProxyTarget,
PublicInstanceProxyHandlers, PublicInstanceProxyHandlers,
RuntimeCompiledPublicInstanceProxyHandlers, RuntimeCompiledPublicInstanceProxyHandlers,
createDevProxyTarget, createRenderContext,
exposePropsOnDevProxyTarget, exposePropsOnRenderContext,
exposeSetupStateOnDevProxyTarget exposeSetupStateOnRenderContext
} from './componentProxy' } from './componentProxy'
import { ComponentPropsOptions, initProps } from './componentProps' import { ComponentPropsOptions, initProps } from './componentProps'
import { Slots, initSlots, InternalSlots } from './componentSlots' import { Slots, initSlots, InternalSlots } from './componentSlots'
@ -136,13 +135,23 @@ export interface ComponentInternalInstance {
components: Record<string, Component> components: Record<string, Component>
directives: Record<string, Directive> directives: Record<string, Directive>
// the rest are only for stateful components // the rest are only for stateful components ---------------------------------
renderContext: Data
// 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 data: Data
props: Data props: Data
attrs: Data attrs: Data
slots: InternalSlots slots: InternalSlots
proxy: ComponentPublicInstance | null
refs: Data refs: Data
emit: EmitFn emit: EmitFn
@ -150,16 +159,6 @@ export interface ComponentInternalInstance {
setupState: Data setupState: Data
setupContext: SetupContext | null 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 related
suspense: SuspenseBoundary | null suspense: SuspenseBoundary | null
asyncDep: Promise<any> | null asyncDep: Promise<any> | null
@ -211,7 +210,6 @@ export function createComponentInstance(
update: null!, // will be set synchronously right after creation update: null!, // will be set synchronously right after creation
render: null, render: null,
proxy: null, proxy: null,
proxyTarget: null!, // to be immediately set
withProxy: null, withProxy: null,
effects: null, effects: null,
provides: parent ? parent.provides : Object.create(appContext.provides), provides: parent ? parent.provides : Object.create(appContext.provides),
@ -219,7 +217,7 @@ export function createComponentInstance(
renderCache: [], renderCache: [],
// state // state
renderContext: EMPTY_OBJ, ctx: EMPTY_OBJ,
data: EMPTY_OBJ, data: EMPTY_OBJ,
props: EMPTY_OBJ, props: EMPTY_OBJ,
attrs: EMPTY_OBJ, attrs: EMPTY_OBJ,
@ -258,9 +256,9 @@ export function createComponentInstance(
emit: null as any // to be set immediately emit: null as any // to be set immediately
} }
if (__DEV__) { if (__DEV__) {
instance.proxyTarget = createDevProxyTarget(instance) instance.ctx = createRenderContext(instance)
} else { } else {
instance.proxyTarget = { _: instance } instance.ctx = { _: instance }
} }
instance.root = parent ? parent.root : instance instance.root = parent ? parent.root : instance
instance.emit = emit.bind(null, instance) instance.emit = emit.bind(null, instance)
@ -335,9 +333,9 @@ function setupStatefulComponent(
// 0. create render proxy property access cache // 0. create render proxy property access cache
instance.accessCache = {} instance.accessCache = {}
// 1. create public instance / render proxy // 1. create public instance / render proxy
instance.proxy = new Proxy(instance.proxyTarget, PublicInstanceProxyHandlers) instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers)
if (__DEV__) { if (__DEV__) {
exposePropsOnDevProxyTarget(instance) exposePropsOnRenderContext(instance)
} }
// 2. call setup() // 2. call setup()
const { setup } = Component const { setup } = Component
@ -399,7 +397,7 @@ export function handleSetupResult(
// assuming a render function compiled from template is present. // assuming a render function compiled from template is present.
instance.setupState = reactive(setupResult) instance.setupState = reactive(setupResult)
if (__DEV__) { if (__DEV__) {
exposeSetupStateOnDevProxyTarget(instance) exposeSetupStateOnRenderContext(instance)
} }
} else if (__DEV__ && setupResult !== undefined) { } else if (__DEV__ && setupResult !== undefined) {
warn( warn(
@ -469,7 +467,7 @@ function finishComponentSetup(
// also only allows a whitelist of globals to fallthrough. // also only allows a whitelist of globals to fallthrough.
if (instance.render._rc) { if (instance.render._rc) {
instance.withProxy = new Proxy( instance.withProxy = new Proxy(
instance.proxyTarget, instance.ctx,
RuntimeCompiledPublicInstanceProxyHandlers RuntimeCompiledPublicInstanceProxyHandlers
) )
} }

View File

@ -39,9 +39,7 @@ import {
import { import {
reactive, reactive,
ComputedGetter, ComputedGetter,
WritableComputedOptions, WritableComputedOptions
ComputedRef,
toRaw
} from '@vue/reactivity' } from '@vue/reactivity'
import { import {
ComponentObjectPropsOptions, ComponentObjectPropsOptions,
@ -246,8 +244,7 @@ export function applyOptions(
options: ComponentOptions, options: ComponentOptions,
asMixin: boolean = false asMixin: boolean = false
) { ) {
const proxyTarget = instance.proxyTarget const publicThis = instance.proxy!
const ctx = instance.proxy!
const { const {
// composition // composition
mixins, mixins,
@ -277,19 +274,13 @@ export function applyOptions(
errorCaptured errorCaptured
} = options } = options
const renderContext = toRaw( const ctx = instance.ctx
instance.renderContext === EMPTY_OBJ &&
(computedOptions || methods || watchOptions || injectOptions)
? (instance.renderContext = reactive({}))
: instance.renderContext
)
const globalMixins = instance.appContext.mixins const globalMixins = instance.appContext.mixins
// call it only during dev // call it only during dev
// applyOptions is called non-as-mixin once per instance // applyOptions is called non-as-mixin once per instance
if (!asMixin) { if (!asMixin) {
callSyncHook('beforeCreate', options, ctx, globalMixins) callSyncHook('beforeCreate', options, publicThis, globalMixins)
// global mixins are applied first // global mixins are applied first
applyMixins(instance, globalMixins) applyMixins(instance, globalMixins)
} }
@ -318,7 +309,7 @@ export function applyOptions(
`Plain object usage is no longer supported.` `Plain object usage is no longer supported.`
) )
} }
const data = dataOptions.call(ctx, ctx) const data = dataOptions.call(publicThis, publicThis)
if (__DEV__ && isPromise(data)) { if (__DEV__ && isPromise(data)) {
warn( warn(
`data() returned a Promise - note data() cannot be async; If you ` + `data() returned a Promise - note data() cannot be async; If you ` +
@ -332,7 +323,13 @@ export function applyOptions(
if (__DEV__) { if (__DEV__) {
for (const key in data) { for (const key in data) {
checkDuplicateProperties!(OptionTypes.DATA, key) 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) instance.data = reactive(data)
@ -345,37 +342,36 @@ export function applyOptions(
if (computedOptions) { if (computedOptions) {
for (const key in computedOptions) { for (const key in computedOptions) {
const opt = (computedOptions as ComputedOptions)[key] const opt = (computedOptions as ComputedOptions)[key]
if (isFunction(opt)) { const get = isFunction(opt)
renderContext[key] = computed(opt.bind(ctx, ctx)) ? opt.bind(publicThis, publicThis)
} else { : isFunction(opt.get)
const { get, set } = opt ? opt.get.bind(publicThis, publicThis)
if (isFunction(get)) { : NOOP
renderContext[key] = computed({ if (__DEV__ && get === NOOP) {
get: get.bind(ctx, ctx), warn(`Computed property "${key}" has no getter.`)
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 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__) { if (__DEV__) {
checkDuplicateProperties!(OptionTypes.COMPUTED, key) 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
})
}
} }
} }
} }
@ -384,12 +380,9 @@ export function applyOptions(
for (const key in methods) { for (const key in methods) {
const methodHandler = (methods as MethodOptions)[key] const methodHandler = (methods as MethodOptions)[key]
if (isFunction(methodHandler)) { if (isFunction(methodHandler)) {
renderContext[key] = methodHandler.bind(ctx) ctx[key] = methodHandler.bind(publicThis)
if (__DEV__) { if (__DEV__) {
checkDuplicateProperties!(OptionTypes.METHODS, key) checkDuplicateProperties!(OptionTypes.METHODS, key)
if (!(key in proxyTarget)) {
proxyTarget[key] = renderContext[key]
}
} }
} else if (__DEV__) { } else if (__DEV__) {
warn( warn(
@ -402,13 +395,13 @@ export function applyOptions(
if (watchOptions) { if (watchOptions) {
for (const key in watchOptions) { for (const key in watchOptions) {
createWatcher(watchOptions[key], renderContext, ctx, key) createWatcher(watchOptions[key], ctx, publicThis, key)
} }
} }
if (provideOptions) { if (provideOptions) {
const provides = isFunction(provideOptions) const provides = isFunction(provideOptions)
? provideOptions.call(ctx) ? provideOptions.call(publicThis)
: provideOptions : provideOptions
for (const key in provides) { for (const key in provides) {
provide(key, provides[key]) provide(key, provides[key])
@ -419,23 +412,21 @@ export function applyOptions(
if (isArray(injectOptions)) { if (isArray(injectOptions)) {
for (let i = 0; i < injectOptions.length; i++) { for (let i = 0; i < injectOptions.length; i++) {
const key = injectOptions[i] const key = injectOptions[i]
renderContext[key] = inject(key) ctx[key] = inject(key)
if (__DEV__) { if (__DEV__) {
checkDuplicateProperties!(OptionTypes.INJECT, key) checkDuplicateProperties!(OptionTypes.INJECT, key)
proxyTarget[key] = renderContext[key]
} }
} }
} else { } else {
for (const key in injectOptions) { for (const key in injectOptions) {
const opt = injectOptions[key] const opt = injectOptions[key]
if (isObject(opt)) { if (isObject(opt)) {
renderContext[key] = inject(opt.from, opt.default) ctx[key] = inject(opt.from, opt.default)
} else { } else {
renderContext[key] = inject(opt) ctx[key] = inject(opt)
} }
if (__DEV__) { if (__DEV__) {
checkDuplicateProperties!(OptionTypes.INJECT, key) checkDuplicateProperties!(OptionTypes.INJECT, key)
proxyTarget[key] = renderContext[key]
} }
} }
} }
@ -451,40 +442,40 @@ export function applyOptions(
// lifecycle options // lifecycle options
if (!asMixin) { if (!asMixin) {
callSyncHook('created', options, ctx, globalMixins) callSyncHook('created', options, publicThis, globalMixins)
} }
if (beforeMount) { if (beforeMount) {
onBeforeMount(beforeMount.bind(ctx)) onBeforeMount(beforeMount.bind(publicThis))
} }
if (mounted) { if (mounted) {
onMounted(mounted.bind(ctx)) onMounted(mounted.bind(publicThis))
} }
if (beforeUpdate) { if (beforeUpdate) {
onBeforeUpdate(beforeUpdate.bind(ctx)) onBeforeUpdate(beforeUpdate.bind(publicThis))
} }
if (updated) { if (updated) {
onUpdated(updated.bind(ctx)) onUpdated(updated.bind(publicThis))
} }
if (activated) { if (activated) {
onActivated(activated.bind(ctx)) onActivated(activated.bind(publicThis))
} }
if (deactivated) { if (deactivated) {
onDeactivated(deactivated.bind(ctx)) onDeactivated(deactivated.bind(publicThis))
} }
if (errorCaptured) { if (errorCaptured) {
onErrorCaptured(errorCaptured.bind(ctx)) onErrorCaptured(errorCaptured.bind(publicThis))
} }
if (renderTracked) { if (renderTracked) {
onRenderTracked(renderTracked.bind(ctx)) onRenderTracked(renderTracked.bind(publicThis))
} }
if (renderTriggered) { if (renderTriggered) {
onRenderTriggered(renderTriggered.bind(ctx)) onRenderTriggered(renderTriggered.bind(publicThis))
} }
if (beforeUnmount) { if (beforeUnmount) {
onBeforeUnmount(beforeUnmount.bind(ctx)) onBeforeUnmount(beforeUnmount.bind(publicThis))
} }
if (unmounted) { if (unmounted) {
onUnmounted(unmounted.bind(ctx)) onUnmounted(unmounted.bind(publicThis))
} }
} }
@ -533,25 +524,25 @@ function applyMixins(
function createWatcher( function createWatcher(
raw: ComponentWatchOptionItem, raw: ComponentWatchOptionItem,
renderContext: Data, ctx: Data,
ctx: ComponentPublicInstance, publicThis: ComponentPublicInstance,
key: string key: string
) { ) {
const getter = () => (ctx as Data)[key] const getter = () => (publicThis as Data)[key]
if (isString(raw)) { if (isString(raw)) {
const handler = renderContext[raw] const handler = ctx[raw]
if (isFunction(handler)) { if (isFunction(handler)) {
watch(getter, handler as WatchCallback) watch(getter, handler as WatchCallback)
} else if (__DEV__) { } else if (__DEV__) {
warn(`Invalid watch handler specified by key "${raw}"`, handler) warn(`Invalid watch handler specified by key "${raw}"`, handler)
} }
} else if (isFunction(raw)) { } else if (isFunction(raw)) {
watch(getter, raw.bind(ctx)) watch(getter, raw.bind(publicThis))
} else if (isObject(raw)) { } else if (isObject(raw)) {
if (isArray(raw)) { if (isArray(raw)) {
raw.forEach(r => createWatcher(r, renderContext, ctx, key)) raw.forEach(r => createWatcher(r, ctx, publicThis, key))
} else { } else {
watch(getter, raw.handler.bind(ctx), raw) watch(getter, raw.handler.bind(publicThis), raw)
} }
} else if (__DEV__) { } else if (__DEV__) {
warn(`Invalid watch option: "${key}"`) warn(`Invalid watch option: "${key}"`)

View File

@ -83,25 +83,24 @@ const enum AccessTypes {
OTHER OTHER
} }
export interface ComponentPublicProxyTarget { export interface ComponentRenderContext {
[key: string]: any [key: string]: any
_: ComponentInternalInstance _: ComponentInternalInstance
} }
export const PublicInstanceProxyHandlers: ProxyHandler<any> = { export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
get({ _: instance }: ComponentPublicProxyTarget, key: string) { get({ _: instance }: ComponentRenderContext, key: string) {
const { const {
renderContext, ctx,
setupState, setupState,
data, data,
props, props,
accessCache, accessCache,
type, type,
proxyTarget,
appContext appContext
} = instance } = instance
// data / props / renderContext // data / props / ctx
// This getter gets called for every property access on the render context // 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 // 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 // is the multiple hasOwn() calls. It's much faster to do a simple property
@ -116,7 +115,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
case AccessTypes.DATA: case AccessTypes.DATA:
return data[key] return data[key]
case AccessTypes.CONTEXT: case AccessTypes.CONTEXT:
return renderContext[key] return ctx[key]
case AccessTypes.PROPS: case AccessTypes.PROPS:
return props![key] return props![key]
// default: just fallthrough // default: just fallthrough
@ -135,31 +134,30 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
) { ) {
accessCache![key] = AccessTypes.PROPS accessCache![key] = AccessTypes.PROPS
return props![key] return props![key]
} else if (renderContext !== EMPTY_OBJ && hasOwn(renderContext, key)) { } else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {
accessCache![key] = AccessTypes.CONTEXT accessCache![key] = AccessTypes.CONTEXT
return renderContext[key] return ctx[key]
} else { } else {
accessCache![key] = AccessTypes.OTHER accessCache![key] = AccessTypes.OTHER
} }
} }
// public $xxx properties &
// user-attached properties (falls through to proxyTarget)
const publicGetter = publicPropertiesMap[key] const publicGetter = publicPropertiesMap[key]
let cssModule, globalProperties let cssModule, globalProperties
// public $xxx properties
if (publicGetter) { if (publicGetter) {
if (__DEV__ && key === '$attrs') { if (__DEV__ && key === '$attrs') {
markAttrsAccessed() markAttrsAccessed()
} }
return publicGetter(instance) return publicGetter(instance)
} else if (hasOwn(proxyTarget, key)) {
return proxyTarget[key]
} else if ( } else if (
// css module (injected by vue-loader)
(cssModule = type.__cssModules) && (cssModule = type.__cssModules) &&
(cssModule = cssModule[key]) (cssModule = cssModule[key])
) { ) {
return cssModule return cssModule
} else if ( } else if (
// global properties
((globalProperties = appContext.config.globalProperties), ((globalProperties = appContext.config.globalProperties),
hasOwn(globalProperties, key)) hasOwn(globalProperties, key))
) { ) {
@ -173,11 +171,11 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
}, },
set( set(
{ _: instance }: ComponentPublicProxyTarget, { _: instance }: ComponentRenderContext,
key: string, key: string,
value: any value: any
): boolean { ): boolean {
const { data, setupState, renderContext } = instance const { data, setupState, ctx } = instance
if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) { if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
setupState[key] = value setupState[key] = value
} else if (data !== EMPTY_OBJ && hasOwn(data, key)) { } else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
@ -189,9 +187,8 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
instance instance
) )
return false return false
} else if (hasOwn(renderContext, key)) { }
renderContext[key] = value if (key[0] === '$' && key.slice(1) in instance) {
} else if (key[0] === '$' && key.slice(1) in instance) {
__DEV__ && __DEV__ &&
warn( warn(
`Attempting to mutate public property "${key}". ` + `Attempting to mutate public property "${key}". ` +
@ -201,13 +198,13 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
return false return false
} else { } else {
if (__DEV__ && key in instance.appContext.config.globalProperties) { if (__DEV__ && key in instance.appContext.config.globalProperties) {
Object.defineProperty(instance.proxyTarget, key, { Object.defineProperty(ctx, key, {
configurable: true,
enumerable: true, enumerable: true,
configurable: true,
value value
}) })
} else { } else {
instance.proxyTarget[key] = value ctx[key] = value
} }
} }
return true return true
@ -215,16 +212,8 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
has( has(
{ {
_: { _: { data, setupState, accessCache, ctx, type, appContext }
data, }: ComponentRenderContext,
setupState,
accessCache,
renderContext,
type,
proxyTarget,
appContext
}
}: ComponentPublicProxyTarget,
key: string key: string
) { ) {
return ( return (
@ -232,18 +221,15 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
(data !== EMPTY_OBJ && hasOwn(data, key)) || (data !== EMPTY_OBJ && hasOwn(data, key)) ||
(setupState !== EMPTY_OBJ && hasOwn(setupState, key)) || (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) ||
(type.props && hasOwn(normalizePropsOptions(type.props)[0]!, key)) || (type.props && hasOwn(normalizePropsOptions(type.props)[0]!, key)) ||
hasOwn(renderContext, key) || hasOwn(ctx, key) ||
hasOwn(publicPropertiesMap, key) || hasOwn(publicPropertiesMap, key) ||
hasOwn(proxyTarget, key) ||
hasOwn(appContext.config.globalProperties, key) hasOwn(appContext.config.globalProperties, key)
) )
} }
} }
if (__DEV__ && !__TEST__) { if (__DEV__ && !__TEST__) {
PublicInstanceProxyHandlers.ownKeys = ( PublicInstanceProxyHandlers.ownKeys = (target: ComponentRenderContext) => {
target: ComponentPublicProxyTarget
) => {
warn( warn(
`Avoid app logic that relies on enumerating keys on a component instance. ` + `Avoid app logic that relies on enumerating keys on a component instance. ` +
`The keys will be empty in production mode to avoid performance overhead.` `The keys will be empty in production mode to avoid performance overhead.`
@ -254,14 +240,14 @@ if (__DEV__ && !__TEST__) {
export const RuntimeCompiledPublicInstanceProxyHandlers = { export const RuntimeCompiledPublicInstanceProxyHandlers = {
...PublicInstanceProxyHandlers, ...PublicInstanceProxyHandlers,
get(target: ComponentPublicProxyTarget, key: string) { get(target: ComponentRenderContext, key: string) {
// fast path for unscopables when using `with` block // fast path for unscopables when using `with` block
if ((key as any) === Symbol.unscopables) { if ((key as any) === Symbol.unscopables) {
return return
} }
return PublicInstanceProxyHandlers.get!(target, key, target) return PublicInstanceProxyHandlers.get!(target, key, target)
}, },
has(_: ComponentPublicProxyTarget, key: string) { has(_: ComponentRenderContext, key: string) {
return key[0] !== '_' && !isGloballyWhitelisted(key) return key[0] !== '_' && !isGloballyWhitelisted(key)
} }
} }
@ -269,7 +255,7 @@ export const RuntimeCompiledPublicInstanceProxyHandlers = {
// In dev mode, the proxy target exposes the same properties as seen on `this` // 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 // for easier console inspection. In prod mode it will be an empty object so
// these properties definitions can be skipped. // these properties definitions can be skipped.
export function createDevProxyTarget(instance: ComponentInternalInstance) { export function createRenderContext(instance: ComponentInternalInstance) {
const target: Record<string, any> = {} const target: Record<string, any> = {}
// expose internal instance for proxy handlers // expose internal instance for proxy handlers
@ -302,19 +288,20 @@ export function createDevProxyTarget(instance: ComponentInternalInstance) {
}) })
}) })
return target as ComponentPublicProxyTarget return target as ComponentRenderContext
} }
export function exposePropsOnDevProxyTarget( // dev only
export function exposePropsOnRenderContext(
instance: ComponentInternalInstance instance: ComponentInternalInstance
) { ) {
const { const {
proxyTarget, ctx,
type: { props: propsOptions } type: { props: propsOptions }
} = instance } = instance
if (propsOptions) { if (propsOptions) {
Object.keys(normalizePropsOptions(propsOptions)[0]!).forEach(key => { Object.keys(normalizePropsOptions(propsOptions)[0]!).forEach(key => {
Object.defineProperty(proxyTarget, key, { Object.defineProperty(ctx, key, {
enumerable: true, enumerable: true,
configurable: true, configurable: true,
get: () => instance.props[key], get: () => instance.props[key],
@ -324,12 +311,13 @@ export function exposePropsOnDevProxyTarget(
} }
} }
export function exposeSetupStateOnDevProxyTarget( // dev only
export function exposeSetupStateOnRenderContext(
instance: ComponentInternalInstance instance: ComponentInternalInstance
) { ) {
const { proxyTarget, setupState } = instance const { ctx, setupState } = instance
Object.keys(toRaw(setupState)).forEach(key => { Object.keys(toRaw(setupState)).forEach(key => {
Object.defineProperty(proxyTarget, key, { Object.defineProperty(ctx, key, {
enumerable: true, enumerable: true,
configurable: true, configurable: true,
get: () => setupState[key], get: () => setupState[key],

View File

@ -26,7 +26,7 @@ import {
RendererNode RendererNode
} from '../renderer' } from '../renderer'
import { setTransitionHooks } from './BaseTransition' import { setTransitionHooks } from './BaseTransition'
import { ComponentPublicProxyTarget } from '../componentProxy' import { ComponentRenderContext } from '../componentProxy'
type MatchPattern = string | RegExp | string[] | RegExp[] type MatchPattern = string | RegExp | string[] | RegExp[]
@ -40,7 +40,7 @@ type CacheKey = string | number | Component
type Cache = Map<CacheKey, VNode> type Cache = Map<CacheKey, VNode>
type Keys = Set<CacheKey> type Keys = Set<CacheKey>
export interface KeepAliveContext extends ComponentPublicProxyTarget { export interface KeepAliveContext extends ComponentRenderContext {
renderer: RendererInternals renderer: RendererInternals
activate: ( activate: (
vnode: VNode, vnode: VNode,
@ -77,12 +77,12 @@ const KeepAliveImpl = {
const instance = getCurrentInstance()! const instance = getCurrentInstance()!
const parentSuspense = instance.suspense const parentSuspense = instance.suspense
// KeepAlive communicates with the instantiated renderer via the proxyTarget // KeepAlive communicates with the instantiated renderer via the
// as a shared context where the renderer passes in its internals, // ctx where the renderer passes in its internals,
// and the KeepAlive instance exposes activate/deactivate implementations. // and the KeepAlive instance exposes activate/deactivate implementations.
// The whole point of this is to avoid importing KeepAlive directly in the // The whole point of this is to avoid importing KeepAlive directly in the
// renderer to facilitate tree-shaking. // renderer to facilitate tree-shaking.
const sharedContext = instance.proxyTarget as KeepAliveContext const sharedContext = instance.ctx as KeepAliveContext
const { const {
renderer: { renderer: {
p: patch, p: patch,

View File

@ -949,7 +949,7 @@ function baseCreateRenderer(
) => { ) => {
if (n1 == null) { if (n1 == null) {
if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) { if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
;(parentComponent!.proxyTarget as KeepAliveContext).activate( ;(parentComponent!.ctx as KeepAliveContext).activate(
n2, n2,
container, container,
anchor, anchor,
@ -998,7 +998,7 @@ function baseCreateRenderer(
// inject renderer internals for keepAlive // inject renderer internals for keepAlive
if (isKeepAlive(initialVNode)) { if (isKeepAlive(initialVNode)) {
;(instance.proxyTarget as KeepAliveContext).renderer = internals ;(instance.ctx as KeepAliveContext).renderer = internals
} }
// resolve props and slots for setup context // resolve props and slots for setup context
@ -1719,7 +1719,7 @@ function baseCreateRenderer(
if (shapeFlag & ShapeFlags.COMPONENT) { if (shapeFlag & ShapeFlags.COMPONENT) {
if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) { if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
;(parentComponent!.proxyTarget as KeepAliveContext).deactivate(vnode) ;(parentComponent!.ctx as KeepAliveContext).deactivate(vnode)
} else { } else {
unmountComponent(vnode.component!, parentSuspense, doRemove) unmountComponent(vnode.component!, parentSuspense, doRemove)
} }