feat(runtime-core): improve component public instance proxy inspection
This commit is contained in:
@@ -2,7 +2,7 @@ import { ComponentInternalInstance, Data } from './component'
|
||||
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,
|
||||
@@ -61,8 +61,8 @@ const publicPropertiesMap: Record<
|
||||
$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),
|
||||
@@ -77,8 +77,13 @@ const enum AccessTypes {
|
||||
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,
|
||||
@@ -87,7 +92,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
|
||||
type,
|
||||
sink,
|
||||
appContext
|
||||
} = target
|
||||
} = instance
|
||||
|
||||
// data / props / renderContext
|
||||
// This getter gets called for every property access on the render context
|
||||
@@ -133,7 +138,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
|
||||
if (__DEV__ && key === '$attrs') {
|
||||
markAttrsAccessed()
|
||||
}
|
||||
return publicGetter(target)
|
||||
return publicGetter(instance)
|
||||
} else if (hasOwn(sink, key)) {
|
||||
return sink[key]
|
||||
} else if (
|
||||
@@ -154,53 +159,131 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
|
||||
}
|
||||
},
|
||||
|
||||
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
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user