componentProxy.ts 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. import { ComponentInternalInstance, Data } from './component'
  2. import { nextTick } from './scheduler'
  3. import { instanceWatch } from './apiWatch'
  4. import { EMPTY_OBJ, hasOwn, globalsWhitelist } 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. export const PublicInstanceProxyHandlers = {
  49. get(target: ComponentInternalInstance, key: string) {
  50. const { renderContext, data, props, propsProxy } = target
  51. if (data !== EMPTY_OBJ && hasOwn(data, key)) {
  52. return data[key]
  53. } else if (hasOwn(renderContext, key)) {
  54. return renderContext[key]
  55. } else if (hasOwn(props, key)) {
  56. // return the value from propsProxy for ref unwrapping and readonly
  57. return propsProxy![key]
  58. } else if (key === '$el') {
  59. return target.vnode.el
  60. } else if (hasOwn(publicPropertiesMap, key)) {
  61. return target[publicPropertiesMap[key]]
  62. }
  63. // methods are only exposed when options are supported
  64. if (__FEATURE_OPTIONS__) {
  65. switch (key) {
  66. case '$forceUpdate':
  67. return target.update
  68. case '$nextTick':
  69. return nextTick
  70. case '$watch':
  71. return instanceWatch.bind(target)
  72. }
  73. }
  74. return target.user[key]
  75. },
  76. // this trap is only called in browser-compiled render functions that use
  77. // `with (this) {}`
  78. has(_: any, key: string): boolean {
  79. return key[0] !== '_' && !globalsWhitelist.has(key)
  80. },
  81. set(target: ComponentInternalInstance, key: string, value: any): boolean {
  82. const { data, renderContext } = target
  83. if (data !== EMPTY_OBJ && hasOwn(data, key)) {
  84. data[key] = value
  85. } else if (hasOwn(renderContext, key)) {
  86. renderContext[key] = value
  87. } else if (key[0] === '$' && key.slice(1) in target) {
  88. __DEV__ &&
  89. warn(
  90. `Attempting to mutate public property "${key}". ` +
  91. `Properties starting with $ are reserved and readonly.`,
  92. target
  93. )
  94. return false
  95. } else if (key in target.props) {
  96. __DEV__ &&
  97. warn(`Attempting to mutate prop "${key}". Props are readonly.`, target)
  98. return false
  99. } else {
  100. target.user[key] = value
  101. }
  102. return true
  103. }
  104. }