vue3-core/packages/runtime-core/src/componentProxy.ts

175 lines
5.0 KiB
TypeScript
Raw Normal View History

2019-10-22 02:04:42 +08:00
import { ComponentInternalInstance, Data, Emit } from './component'
2019-09-04 11:04:11 +08:00
import { nextTick } from './scheduler'
2019-09-04 23:36:27 +08:00
import { instanceWatch } from './apiWatch'
import { EMPTY_OBJ, hasOwn, isGloballyWhitelisted } from '@vue/shared'
2019-10-22 23:26:48 +08:00
import {
ExtractComputedReturns,
ComponentOptionsBase,
ComputedOptions,
MethodOptions
} from './apiOptions'
import { UnwrapRef, ReactiveEffect } from '@vue/reactivity'
import { warn } from './warning'
2019-10-23 21:29:09 +08:00
import { Slots } from './componentSlots'
import {
currentRenderingInstance,
markAttrsAccessed
} from './componentRenderUtils'
// public properties exposed on the proxy, which is used as the render context
// in templates (as `this` in the render option)
2019-09-07 00:58:31 +08:00
export type ComponentPublicInstance<
P = {},
B = {},
D = {},
2019-10-22 23:26:48 +08:00
C extends ComputedOptions = {},
M extends MethodOptions = {},
PublicProps = P
> = {
$data: D
$props: PublicProps
$attrs: Data
$refs: Data
2019-10-23 21:29:09 +08:00
$slots: Slots
2019-09-07 00:58:31 +08:00
$root: ComponentInternalInstance | null
$parent: ComponentInternalInstance | null
2019-10-22 02:04:42 +08:00
$emit: Emit
$el: any
2019-10-22 23:26:48 +08:00
$options: ComponentOptionsBase<P, B, D, C, M>
$forceUpdate: ReactiveEffect
$nextTick: typeof nextTick
$watch: typeof instanceWatch
} & P &
UnwrapRef<B> &
D &
ExtractComputedReturns<C> &
M
2019-05-29 13:43:46 +08:00
const publicPropertiesMap = {
$data: 'data',
$props: 'propsProxy',
$attrs: 'attrs',
$slots: 'slots',
$refs: 'refs',
$parent: 'parent',
$root: 'root',
$emit: 'emit',
$options: 'type'
}
const enum AccessTypes {
DATA,
CONTEXT,
PROPS
}
export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
2019-09-07 00:58:31 +08:00
get(target: ComponentInternalInstance, key: string) {
// fast path for unscopables when using `with` block
if (__RUNTIME_COMPILE__ && (key as any) === Symbol.unscopables) {
return
}
const {
renderContext,
data,
props,
propsProxy,
accessCache,
type,
2019-10-30 10:28:38 +08:00
sink
} = target
// 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
// access on a plain object, so we use an accessCache object (with null
// prototype) to memoize what access type a key corresponds to.
const n = accessCache![key]
if (n !== undefined) {
switch (n) {
case AccessTypes.DATA:
return data[key]
case AccessTypes.CONTEXT:
return renderContext[key]
case AccessTypes.PROPS:
return propsProxy![key]
}
} else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
accessCache![key] = AccessTypes.DATA
2019-06-19 17:08:42 +08:00
return data[key]
2019-09-06 08:48:14 +08:00
} else if (hasOwn(renderContext, key)) {
accessCache![key] = AccessTypes.CONTEXT
return renderContext[key]
2019-09-06 08:48:14 +08:00
} else if (hasOwn(props, key)) {
2019-10-18 10:29:51 +08:00
// only cache props access if component has declared (thus stable) props
if (type.props != null) {
accessCache![key] = AccessTypes.PROPS
2019-10-18 10:29:51 +08:00
}
// return the value from propsProxy for ref unwrapping and readonly
2019-10-05 22:09:34 +08:00
return propsProxy![key]
} else if (key === '$cache') {
return target.renderCache || (target.renderCache = [])
} else if (key === '$el') {
return target.vnode.el
} else if (hasOwn(publicPropertiesMap, key)) {
if (__DEV__ && key === '$attrs') {
markAttrsAccessed()
}
return target[publicPropertiesMap[key]]
}
// methods are only exposed when options are supported
if (__FEATURE_OPTIONS__) {
2019-05-29 13:43:46 +08:00
switch (key) {
case '$forceUpdate':
return target.update
case '$nextTick':
return nextTick
case '$watch':
return instanceWatch.bind(target)
2019-05-29 13:43:46 +08:00
}
}
2019-10-30 10:28:38 +08:00
if (hasOwn(sink, key)) {
return sink[key]
} else if (__DEV__ && currentRenderingInstance != null) {
warn(
`Property ${JSON.stringify(key)} was accessed during render ` +
`but is not defined on instance.`
)
}
2019-05-29 13:43:46 +08:00
},
2019-09-07 00:58:31 +08:00
set(target: ComponentInternalInstance, key: string, value: any): boolean {
const { data, renderContext } = target
2019-09-06 08:48:14 +08:00
if (data !== EMPTY_OBJ && hasOwn(data, key)) {
2019-06-19 17:08:42 +08:00
data[key] = value
2019-09-06 08:48:14 +08:00
} else if (hasOwn(renderContext, key)) {
renderContext[key] = value
2019-08-21 21:50:20 +08:00
} else if (key[0] === '$' && key.slice(1) in target) {
__DEV__ &&
warn(
`Attempting to mutate public property "${key}". ` +
`Properties starting with $ are reserved and readonly.`,
target
)
2019-08-21 21:50:20 +08:00
return false
} else if (key in target.props) {
__DEV__ &&
warn(`Attempting to mutate prop "${key}". Props are readonly.`, target)
2019-08-21 21:50:20 +08:00
return false
2019-05-29 13:43:46 +08:00
} else {
2019-10-30 10:28:38 +08:00
target.sink[key] = value
2019-05-29 13:43:46 +08:00
}
return true
2019-05-29 13:43:46 +08:00
}
}
if (__RUNTIME_COMPILE__) {
// this trap is only called in browser-compiled render functions that use
// `with (this) {}`
2019-10-22 23:26:48 +08:00
PublicInstanceProxyHandlers.has = (
_: ComponentInternalInstance,
key: string
): boolean => {
return key[0] !== '_' && !isGloballyWhitelisted(key)
}
}