componentProxy.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. import { ComponentInternalInstance, Data, Emit } from './component'
  2. import { nextTick } from './scheduler'
  3. import { instanceWatch } from './apiWatch'
  4. import { EMPTY_OBJ, hasOwn, isGloballyWhitelisted } from '@vue/shared'
  5. import {
  6. ExtractComputedReturns,
  7. ComponentOptionsBase,
  8. ComputedOptions,
  9. MethodOptions
  10. } from './apiOptions'
  11. import { UnwrapRef, ReactiveEffect } from '@vue/reactivity'
  12. import { warn } from './warning'
  13. import { Slots } from './componentSlots'
  14. import {
  15. currentRenderingInstance,
  16. markAttrsAccessed
  17. } from './componentRenderUtils'
  18. // public properties exposed on the proxy, which is used as the render context
  19. // in templates (as `this` in the render option)
  20. export type ComponentPublicInstance<
  21. P = {},
  22. B = {},
  23. D = {},
  24. C extends ComputedOptions = {},
  25. M extends MethodOptions = {},
  26. PublicProps = P
  27. > = {
  28. $data: D
  29. $props: PublicProps
  30. $attrs: Data
  31. $refs: Data
  32. $slots: Slots
  33. $root: ComponentInternalInstance | null
  34. $parent: ComponentInternalInstance | null
  35. $emit: Emit
  36. $el: any
  37. $options: ComponentOptionsBase<P, B, D, C, M>
  38. $forceUpdate: ReactiveEffect
  39. $nextTick: typeof nextTick
  40. $watch: typeof instanceWatch
  41. } & P &
  42. UnwrapRef<B> &
  43. D &
  44. ExtractComputedReturns<C> &
  45. M
  46. const publicPropertiesMap: Record<
  47. string,
  48. (i: ComponentInternalInstance) => any
  49. > = {
  50. $: i => i,
  51. $el: i => i.vnode.el,
  52. $cache: i => i.renderCache,
  53. $data: i => i.data,
  54. $props: i => i.propsProxy,
  55. $attrs: i => i.attrs,
  56. $slots: i => i.slots,
  57. $refs: i => i.refs,
  58. $parent: i => i.parent,
  59. $root: i => i.root,
  60. $emit: i => i.emit,
  61. $options: i => i.type,
  62. $forceUpdate: i => i.update,
  63. $nextTick: () => nextTick,
  64. $watch: i => instanceWatch.bind(i)
  65. }
  66. const enum AccessTypes {
  67. DATA,
  68. CONTEXT,
  69. PROPS
  70. }
  71. export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
  72. get(target: ComponentInternalInstance, key: string) {
  73. // fast path for unscopables when using `with` block
  74. if (__RUNTIME_COMPILE__ && (key as any) === Symbol.unscopables) {
  75. return
  76. }
  77. const {
  78. renderContext,
  79. data,
  80. props,
  81. propsProxy,
  82. accessCache,
  83. type,
  84. sink
  85. } = target
  86. // data / props / renderContext
  87. // This getter gets called for every property access on the render context
  88. // during render and is a major hotspot. The most expensive part of this
  89. // is the multiple hasOwn() calls. It's much faster to do a simple property
  90. // access on a plain object, so we use an accessCache object (with null
  91. // prototype) to memoize what access type a key corresponds to.
  92. const n = accessCache![key]
  93. if (n !== undefined) {
  94. switch (n) {
  95. case AccessTypes.DATA:
  96. return data[key]
  97. case AccessTypes.CONTEXT:
  98. return renderContext[key]
  99. case AccessTypes.PROPS:
  100. return propsProxy![key]
  101. }
  102. } else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
  103. accessCache![key] = AccessTypes.DATA
  104. return data[key]
  105. } else if (hasOwn(renderContext, key)) {
  106. accessCache![key] = AccessTypes.CONTEXT
  107. return renderContext[key]
  108. } else if (hasOwn(props, key)) {
  109. // only cache props access if component has declared (thus stable) props
  110. if (type.props != null) {
  111. accessCache![key] = AccessTypes.PROPS
  112. }
  113. // return the value from propsProxy for ref unwrapping and readonly
  114. return propsProxy![key]
  115. }
  116. // public $xxx properties & user-attached properties (sink)
  117. const publicGetter = publicPropertiesMap[key]
  118. if (publicGetter !== undefined) {
  119. if (__DEV__ && key === '$attrs') {
  120. markAttrsAccessed()
  121. }
  122. return publicGetter(target)
  123. } else if (hasOwn(sink, key)) {
  124. return sink[key]
  125. } else if (__DEV__ && currentRenderingInstance != null) {
  126. warn(
  127. `Property ${JSON.stringify(key)} was accessed during render ` +
  128. `but is not defined on instance.`
  129. )
  130. }
  131. },
  132. has(target: ComponentInternalInstance, key: string) {
  133. const { data, accessCache, renderContext, type, sink } = target
  134. return (
  135. accessCache![key] !== undefined ||
  136. (data !== EMPTY_OBJ && hasOwn(data, key)) ||
  137. hasOwn(renderContext, key) ||
  138. (type.props != null && hasOwn(type.props, key)) ||
  139. hasOwn(publicPropertiesMap, key) ||
  140. hasOwn(sink, key)
  141. )
  142. },
  143. set(target: ComponentInternalInstance, key: string, value: any): boolean {
  144. const { data, renderContext } = target
  145. if (data !== EMPTY_OBJ && hasOwn(data, key)) {
  146. data[key] = value
  147. } else if (hasOwn(renderContext, key)) {
  148. renderContext[key] = value
  149. } else if (key[0] === '$' && key.slice(1) in target) {
  150. __DEV__ &&
  151. warn(
  152. `Attempting to mutate public property "${key}". ` +
  153. `Properties starting with $ are reserved and readonly.`,
  154. target
  155. )
  156. return false
  157. } else if (key in target.props) {
  158. __DEV__ &&
  159. warn(`Attempting to mutate prop "${key}". Props are readonly.`, target)
  160. return false
  161. } else {
  162. target.sink[key] = value
  163. }
  164. return true
  165. }
  166. }
  167. export const runtimeCompiledRenderProxyHandlers = {
  168. ...PublicInstanceProxyHandlers,
  169. has(_target: ComponentInternalInstance, key: string) {
  170. return key[0] !== '_' && !isGloballyWhitelisted(key)
  171. }
  172. }