componentProxy.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. import { ComponentInternalInstance, Data } from './component'
  2. import { nextTick } from './scheduler'
  3. import { instanceWatch } from './apiWatch'
  4. import { EMPTY_OBJ, hasOwn, isGloballyWhitelisted } from '@vue/shared'
  5. import { ExtractComputedReturns } from './apiOptions'
  6. import { UnwrapRef, ReactiveEffect } from '@vue/reactivity'
  7. import { warn } from './warning'
  8. // public properties exposed on the proxy, which is used as the render context
  9. // in templates (as `this` in the render option)
  10. export type ComponentPublicInstance<
  11. P = {},
  12. B = {},
  13. D = {},
  14. C = {},
  15. M = {},
  16. PublicProps = P
  17. > = {
  18. [key: string]: any
  19. $data: D
  20. $props: PublicProps
  21. $attrs: Data
  22. $refs: Data
  23. $slots: Data
  24. $root: ComponentInternalInstance | null
  25. $parent: ComponentInternalInstance | null
  26. $emit: (event: string, ...args: unknown[]) => void
  27. $el: any
  28. $options: any
  29. $forceUpdate: ReactiveEffect
  30. $nextTick: typeof nextTick
  31. $watch: typeof instanceWatch
  32. } & P &
  33. UnwrapRef<B> &
  34. D &
  35. ExtractComputedReturns<C> &
  36. M
  37. const publicPropertiesMap = {
  38. $data: 'data',
  39. $props: 'propsProxy',
  40. $attrs: 'attrs',
  41. $slots: 'slots',
  42. $refs: 'refs',
  43. $parent: 'parent',
  44. $root: 'root',
  45. $emit: 'emit',
  46. $options: 'type'
  47. }
  48. const enum AccessTypes {
  49. DATA,
  50. CONTEXT,
  51. PROPS
  52. }
  53. export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
  54. get(target: ComponentInternalInstance, key: string) {
  55. const { renderContext, data, props, propsProxy, accessCache, type } = target
  56. // This getter gets called for every property access on the render context
  57. // during render and is a major hotspot. The most expensive part of this
  58. // is the multiple hasOwn() calls. It's much faster to do a simple property
  59. // access on a plain object, so we use an accessCache object (with null
  60. // prototype) to memoize what access type a key corresponds to.
  61. const n = accessCache[key]
  62. if (n !== undefined) {
  63. switch (n) {
  64. case AccessTypes.DATA:
  65. return data[key]
  66. case AccessTypes.CONTEXT:
  67. return renderContext[key]
  68. case AccessTypes.PROPS:
  69. return propsProxy![key]
  70. }
  71. } else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
  72. accessCache[key] = AccessTypes.DATA
  73. return data[key]
  74. } else if (hasOwn(renderContext, key)) {
  75. accessCache[key] = AccessTypes.CONTEXT
  76. return renderContext[key]
  77. } else if (hasOwn(props, key)) {
  78. // only cache props access if component has declared (thus stable) props
  79. if (type.props != null) {
  80. accessCache[key] = AccessTypes.PROPS
  81. }
  82. // return the value from propsProxy for ref unwrapping and readonly
  83. return propsProxy![key]
  84. } else if (key === '$el') {
  85. return target.vnode.el
  86. } else if (hasOwn(publicPropertiesMap, key)) {
  87. return target[publicPropertiesMap[key]]
  88. }
  89. // methods are only exposed when options are supported
  90. if (__FEATURE_OPTIONS__) {
  91. switch (key) {
  92. case '$forceUpdate':
  93. return target.update
  94. case '$nextTick':
  95. return nextTick
  96. case '$watch':
  97. return instanceWatch.bind(target)
  98. }
  99. }
  100. return target.user[key]
  101. },
  102. set(target: ComponentInternalInstance, key: string, value: any): boolean {
  103. const { data, renderContext } = target
  104. if (data !== EMPTY_OBJ && hasOwn(data, key)) {
  105. data[key] = value
  106. } else if (hasOwn(renderContext, key)) {
  107. renderContext[key] = value
  108. } else if (key[0] === '$' && key.slice(1) in target) {
  109. __DEV__ &&
  110. warn(
  111. `Attempting to mutate public property "${key}". ` +
  112. `Properties starting with $ are reserved and readonly.`,
  113. target
  114. )
  115. return false
  116. } else if (key in target.props) {
  117. __DEV__ &&
  118. warn(`Attempting to mutate prop "${key}". Props are readonly.`, target)
  119. return false
  120. } else {
  121. target.user[key] = value
  122. }
  123. return true
  124. }
  125. }
  126. if (__RUNTIME_COMPILE__) {
  127. // this trap is only called in browser-compiled render functions that use
  128. // `with (this) {}`
  129. PublicInstanceProxyHandlers.has = (_: any, key: string): boolean => {
  130. return key[0] !== '_' && !isGloballyWhitelisted(key)
  131. }
  132. }