componentPublicInstance.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. import { ComponentInternalInstance, Data } from './component'
  2. import { nextTick, queueJob } from './scheduler'
  3. import { instanceWatch, WatchOptions, WatchStopHandle } from './apiWatch'
  4. import {
  5. EMPTY_OBJ,
  6. hasOwn,
  7. isGloballyWhitelisted,
  8. NOOP,
  9. extend,
  10. isString
  11. } from '@vue/shared'
  12. import {
  13. ReactiveEffect,
  14. toRaw,
  15. shallowReadonly,
  16. ReactiveFlags,
  17. track,
  18. TrackOpTypes,
  19. ShallowUnwrapRef
  20. } from '@vue/reactivity'
  21. import {
  22. ExtractComputedReturns,
  23. ComponentOptionsBase,
  24. ComputedOptions,
  25. MethodOptions,
  26. ComponentOptionsMixin,
  27. OptionTypesType,
  28. OptionTypesKeys,
  29. resolveMergedOptions,
  30. isInBeforeCreate
  31. } from './componentOptions'
  32. import { EmitsOptions, EmitFn } from './componentEmits'
  33. import { Slots } from './componentSlots'
  34. import {
  35. currentRenderingInstance,
  36. markAttrsAccessed
  37. } from './componentRenderUtils'
  38. import { warn } from './warning'
  39. import { UnionToIntersection } from './helpers/typeUtils'
  40. /**
  41. * Custom properties added to component instances in any way and can be accessed through `this`
  42. *
  43. * @example
  44. * Here is an example of adding a property `$router` to every component instance:
  45. * ```ts
  46. * import { createApp } from 'vue'
  47. * import { Router, createRouter } from 'vue-router'
  48. *
  49. * declare module '@vue/runtime-core' {
  50. * interface ComponentCustomProperties {
  51. * $router: Router
  52. * }
  53. * }
  54. *
  55. * // effectively adding the router to every component instance
  56. * const app = createApp({})
  57. * const router = createRouter()
  58. * app.config.globalProperties.$router = router
  59. *
  60. * const vm = app.mount('#app')
  61. * // we can access the router from the instance
  62. * vm.$router.push('/')
  63. * ```
  64. */
  65. export interface ComponentCustomProperties {}
  66. type IsDefaultMixinComponent<T> = T extends ComponentOptionsMixin
  67. ? ComponentOptionsMixin extends T ? true : false
  68. : false
  69. type MixinToOptionTypes<T> = T extends ComponentOptionsBase<
  70. infer P,
  71. infer B,
  72. infer D,
  73. infer C,
  74. infer M,
  75. infer Mixin,
  76. infer Extends,
  77. any,
  78. any,
  79. infer Defaults
  80. >
  81. ? OptionTypesType<P & {}, B & {}, D & {}, C & {}, M & {}, Defaults & {}> &
  82. IntersectionMixin<Mixin> &
  83. IntersectionMixin<Extends>
  84. : never
  85. // ExtractMixin(map type) is used to resolve circularly references
  86. type ExtractMixin<T> = {
  87. Mixin: MixinToOptionTypes<T>
  88. }[T extends ComponentOptionsMixin ? 'Mixin' : never]
  89. type IntersectionMixin<T> = IsDefaultMixinComponent<T> extends true
  90. ? OptionTypesType<{}, {}, {}, {}, {}>
  91. : UnionToIntersection<ExtractMixin<T>>
  92. type UnwrapMixinsType<
  93. T,
  94. Type extends OptionTypesKeys
  95. > = T extends OptionTypesType ? T[Type] : never
  96. type EnsureNonVoid<T> = T extends void ? {} : T
  97. export type ComponentPublicInstanceConstructor<
  98. T extends ComponentPublicInstance<
  99. Props,
  100. RawBindings,
  101. D,
  102. C,
  103. M
  104. > = ComponentPublicInstance<any>,
  105. Props = any,
  106. RawBindings = any,
  107. D = any,
  108. C extends ComputedOptions = ComputedOptions,
  109. M extends MethodOptions = MethodOptions
  110. > = {
  111. __isFragment?: never
  112. __isTeleport?: never
  113. __isSuspense?: never
  114. new (...args: any[]): T
  115. }
  116. export type CreateComponentPublicInstance<
  117. P = {},
  118. B = {},
  119. D = {},
  120. C extends ComputedOptions = {},
  121. M extends MethodOptions = {},
  122. Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
  123. Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
  124. E extends EmitsOptions = {},
  125. PublicProps = P,
  126. Defaults = {},
  127. MakeDefaultsOptional extends boolean = false,
  128. PublicMixin = IntersectionMixin<Mixin> & IntersectionMixin<Extends>,
  129. PublicP = UnwrapMixinsType<PublicMixin, 'P'> & EnsureNonVoid<P>,
  130. PublicB = UnwrapMixinsType<PublicMixin, 'B'> & EnsureNonVoid<B>,
  131. PublicD = UnwrapMixinsType<PublicMixin, 'D'> & EnsureNonVoid<D>,
  132. PublicC extends ComputedOptions = UnwrapMixinsType<PublicMixin, 'C'> &
  133. EnsureNonVoid<C>,
  134. PublicM extends MethodOptions = UnwrapMixinsType<PublicMixin, 'M'> &
  135. EnsureNonVoid<M>,
  136. PublicDefaults = UnwrapMixinsType<PublicMixin, 'Defaults'> &
  137. EnsureNonVoid<Defaults>
  138. > = ComponentPublicInstance<
  139. PublicP,
  140. PublicB,
  141. PublicD,
  142. PublicC,
  143. PublicM,
  144. E,
  145. PublicProps,
  146. PublicDefaults,
  147. MakeDefaultsOptional,
  148. ComponentOptionsBase<P, B, D, C, M, Mixin, Extends, E, string, Defaults>
  149. >
  150. // public properties exposed on the proxy, which is used as the render context
  151. // in templates (as `this` in the render option)
  152. export type ComponentPublicInstance<
  153. P = {}, // props type extracted from props option
  154. B = {}, // raw bindings returned from setup()
  155. D = {}, // return from data()
  156. C extends ComputedOptions = {},
  157. M extends MethodOptions = {},
  158. E extends EmitsOptions = {},
  159. PublicProps = P,
  160. Defaults = {},
  161. MakeDefaultsOptional extends boolean = false,
  162. Options = ComponentOptionsBase<any, any, any, any, any, any, any, any, any>
  163. > = {
  164. $: ComponentInternalInstance
  165. $data: D
  166. $props: MakeDefaultsOptional extends true
  167. ? Partial<Defaults> & Omit<P & PublicProps, keyof Defaults>
  168. : P & PublicProps
  169. $attrs: Data
  170. $refs: Data
  171. $slots: Slots
  172. $root: ComponentPublicInstance | null
  173. $parent: ComponentPublicInstance | null
  174. $emit: EmitFn<E>
  175. $el: any
  176. $options: Options
  177. $forceUpdate: ReactiveEffect
  178. $nextTick: typeof nextTick
  179. $watch(
  180. source: string | Function,
  181. cb: Function,
  182. options?: WatchOptions
  183. ): WatchStopHandle
  184. } & P &
  185. ShallowUnwrapRef<B> &
  186. D &
  187. ExtractComputedReturns<C> &
  188. M &
  189. ComponentCustomProperties
  190. type PublicPropertiesMap = Record<string, (i: ComponentInternalInstance) => any>
  191. /**
  192. * #2437 In Vue 3, functional components do not have a public instance proxy but
  193. * they exist in the internal parent chain. For code that relies on traversing
  194. * public $parent chains, skip functional ones and go to the parent instead.
  195. */
  196. const getPublicInstance = (
  197. i: ComponentInternalInstance | null
  198. ): ComponentPublicInstance | null =>
  199. i && (i.proxy ? i.proxy : getPublicInstance(i.parent))
  200. const publicPropertiesMap: PublicPropertiesMap = extend(Object.create(null), {
  201. $: i => i,
  202. $el: i => i.vnode.el,
  203. $data: i => i.data,
  204. $props: i => (__DEV__ ? shallowReadonly(i.props) : i.props),
  205. $attrs: i => (__DEV__ ? shallowReadonly(i.attrs) : i.attrs),
  206. $slots: i => (__DEV__ ? shallowReadonly(i.slots) : i.slots),
  207. $refs: i => (__DEV__ ? shallowReadonly(i.refs) : i.refs),
  208. $parent: i => getPublicInstance(i.parent),
  209. $root: i => i.root && i.root.proxy,
  210. $emit: i => i.emit,
  211. $options: i => (__FEATURE_OPTIONS_API__ ? resolveMergedOptions(i) : i.type),
  212. $forceUpdate: i => () => queueJob(i.update),
  213. $nextTick: i => nextTick.bind(i.proxy!),
  214. $watch: i => (__FEATURE_OPTIONS_API__ ? instanceWatch.bind(i) : NOOP)
  215. } as PublicPropertiesMap)
  216. const enum AccessTypes {
  217. SETUP,
  218. DATA,
  219. PROPS,
  220. CONTEXT,
  221. OTHER
  222. }
  223. export interface ComponentRenderContext {
  224. [key: string]: any
  225. _: ComponentInternalInstance
  226. }
  227. export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
  228. get({ _: instance }: ComponentRenderContext, key: string) {
  229. const {
  230. ctx,
  231. setupState,
  232. data,
  233. props,
  234. accessCache,
  235. type,
  236. appContext
  237. } = instance
  238. // let @vue/reactivity know it should never observe Vue public instances.
  239. if (key === ReactiveFlags.SKIP) {
  240. return true
  241. }
  242. // for internal formatters to know that this is a Vue instance
  243. if (__DEV__ && key === '__isVue') {
  244. return true
  245. }
  246. // data / props / ctx
  247. // This getter gets called for every property access on the render context
  248. // during render and is a major hotspot. The most expensive part of this
  249. // is the multiple hasOwn() calls. It's much faster to do a simple property
  250. // access on a plain object, so we use an accessCache object (with null
  251. // prototype) to memoize what access type a key corresponds to.
  252. let normalizedProps
  253. if (key[0] !== '$') {
  254. const n = accessCache![key]
  255. if (n !== undefined) {
  256. switch (n) {
  257. case AccessTypes.SETUP:
  258. return setupState[key]
  259. case AccessTypes.DATA:
  260. return data[key]
  261. case AccessTypes.CONTEXT:
  262. return ctx[key]
  263. case AccessTypes.PROPS:
  264. return props![key]
  265. // default: just fallthrough
  266. }
  267. } else if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
  268. accessCache![key] = AccessTypes.SETUP
  269. return setupState[key]
  270. } else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
  271. accessCache![key] = AccessTypes.DATA
  272. return data[key]
  273. } else if (
  274. // only cache other properties when instance has declared (thus stable)
  275. // props
  276. (normalizedProps = instance.propsOptions[0]) &&
  277. hasOwn(normalizedProps, key)
  278. ) {
  279. accessCache![key] = AccessTypes.PROPS
  280. return props![key]
  281. } else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {
  282. accessCache![key] = AccessTypes.CONTEXT
  283. return ctx[key]
  284. } else if (!__FEATURE_OPTIONS_API__ || !isInBeforeCreate) {
  285. accessCache![key] = AccessTypes.OTHER
  286. }
  287. }
  288. const publicGetter = publicPropertiesMap[key]
  289. let cssModule, globalProperties
  290. // public $xxx properties
  291. if (publicGetter) {
  292. if (key === '$attrs') {
  293. track(instance, TrackOpTypes.GET, key)
  294. __DEV__ && markAttrsAccessed()
  295. }
  296. return publicGetter(instance)
  297. } else if (
  298. // css module (injected by vue-loader)
  299. (cssModule = type.__cssModules) &&
  300. (cssModule = cssModule[key])
  301. ) {
  302. return cssModule
  303. } else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {
  304. // user may set custom properties to `this` that start with `$`
  305. accessCache![key] = AccessTypes.CONTEXT
  306. return ctx[key]
  307. } else if (
  308. // global properties
  309. ((globalProperties = appContext.config.globalProperties),
  310. hasOwn(globalProperties, key))
  311. ) {
  312. return globalProperties[key]
  313. } else if (
  314. __DEV__ &&
  315. currentRenderingInstance &&
  316. (!isString(key) ||
  317. // #1091 avoid internal isRef/isVNode checks on component instance leading
  318. // to infinite warning loop
  319. key.indexOf('__v') !== 0)
  320. ) {
  321. if (
  322. data !== EMPTY_OBJ &&
  323. (key[0] === '$' || key[0] === '_') &&
  324. hasOwn(data, key)
  325. ) {
  326. warn(
  327. `Property ${JSON.stringify(
  328. key
  329. )} must be accessed via $data because it starts with a reserved ` +
  330. `character ("$" or "_") and is not proxied on the render context.`
  331. )
  332. } else {
  333. warn(
  334. `Property ${JSON.stringify(key)} was accessed during render ` +
  335. `but is not defined on instance.`
  336. )
  337. }
  338. }
  339. },
  340. set(
  341. { _: instance }: ComponentRenderContext,
  342. key: string,
  343. value: any
  344. ): boolean {
  345. const { data, setupState, ctx } = instance
  346. if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
  347. setupState[key] = value
  348. } else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
  349. data[key] = value
  350. } else if (key in instance.props) {
  351. __DEV__ &&
  352. warn(
  353. `Attempting to mutate prop "${key}". Props are readonly.`,
  354. instance
  355. )
  356. return false
  357. }
  358. if (key[0] === '$' && key.slice(1) in instance) {
  359. __DEV__ &&
  360. warn(
  361. `Attempting to mutate public property "${key}". ` +
  362. `Properties starting with $ are reserved and readonly.`,
  363. instance
  364. )
  365. return false
  366. } else {
  367. if (__DEV__ && key in instance.appContext.config.globalProperties) {
  368. Object.defineProperty(ctx, key, {
  369. enumerable: true,
  370. configurable: true,
  371. value
  372. })
  373. } else {
  374. ctx[key] = value
  375. }
  376. }
  377. return true
  378. },
  379. has(
  380. {
  381. _: { data, setupState, accessCache, ctx, appContext, propsOptions }
  382. }: ComponentRenderContext,
  383. key: string
  384. ) {
  385. let normalizedProps
  386. return (
  387. accessCache![key] !== undefined ||
  388. (data !== EMPTY_OBJ && hasOwn(data, key)) ||
  389. (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) ||
  390. ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||
  391. hasOwn(ctx, key) ||
  392. hasOwn(publicPropertiesMap, key) ||
  393. hasOwn(appContext.config.globalProperties, key)
  394. )
  395. }
  396. }
  397. if (__DEV__ && !__TEST__) {
  398. PublicInstanceProxyHandlers.ownKeys = (target: ComponentRenderContext) => {
  399. warn(
  400. `Avoid app logic that relies on enumerating keys on a component instance. ` +
  401. `The keys will be empty in production mode to avoid performance overhead.`
  402. )
  403. return Reflect.ownKeys(target)
  404. }
  405. }
  406. export const RuntimeCompiledPublicInstanceProxyHandlers = extend(
  407. {},
  408. PublicInstanceProxyHandlers,
  409. {
  410. get(target: ComponentRenderContext, key: string) {
  411. // fast path for unscopables when using `with` block
  412. if ((key as any) === Symbol.unscopables) {
  413. return
  414. }
  415. return PublicInstanceProxyHandlers.get!(target, key, target)
  416. },
  417. has(_: ComponentRenderContext, key: string) {
  418. const has = key[0] !== '_' && !isGloballyWhitelisted(key)
  419. if (__DEV__ && !has && PublicInstanceProxyHandlers.has!(_, key)) {
  420. warn(
  421. `Property ${JSON.stringify(
  422. key
  423. )} should not start with _ which is a reserved prefix for Vue internals.`
  424. )
  425. }
  426. return has
  427. }
  428. }
  429. )
  430. // In dev mode, the proxy target exposes the same properties as seen on `this`
  431. // for easier console inspection. In prod mode it will be an empty object so
  432. // these properties definitions can be skipped.
  433. export function createRenderContext(instance: ComponentInternalInstance) {
  434. const target: Record<string, any> = {}
  435. // expose internal instance for proxy handlers
  436. Object.defineProperty(target, `_`, {
  437. configurable: true,
  438. enumerable: false,
  439. get: () => instance
  440. })
  441. // expose public properties
  442. Object.keys(publicPropertiesMap).forEach(key => {
  443. Object.defineProperty(target, key, {
  444. configurable: true,
  445. enumerable: false,
  446. get: () => publicPropertiesMap[key](instance),
  447. // intercepted by the proxy so no need for implementation,
  448. // but needed to prevent set errors
  449. set: NOOP
  450. })
  451. })
  452. // expose global properties
  453. const { globalProperties } = instance.appContext.config
  454. Object.keys(globalProperties).forEach(key => {
  455. Object.defineProperty(target, key, {
  456. configurable: true,
  457. enumerable: false,
  458. get: () => globalProperties[key],
  459. set: NOOP
  460. })
  461. })
  462. return target as ComponentRenderContext
  463. }
  464. // dev only
  465. export function exposePropsOnRenderContext(
  466. instance: ComponentInternalInstance
  467. ) {
  468. const {
  469. ctx,
  470. propsOptions: [propsOptions]
  471. } = instance
  472. if (propsOptions) {
  473. Object.keys(propsOptions).forEach(key => {
  474. Object.defineProperty(ctx, key, {
  475. enumerable: true,
  476. configurable: true,
  477. get: () => instance.props[key],
  478. set: NOOP
  479. })
  480. })
  481. }
  482. }
  483. // dev only
  484. export function exposeSetupStateOnRenderContext(
  485. instance: ComponentInternalInstance
  486. ) {
  487. const { ctx, setupState } = instance
  488. Object.keys(toRaw(setupState)).forEach(key => {
  489. if (key[0] === '$' || key[0] === '_') {
  490. warn(
  491. `setup() return property ${JSON.stringify(
  492. key
  493. )} should not start with "$" or "_" ` +
  494. `which are reserved prefixes for Vue internals.`
  495. )
  496. return
  497. }
  498. Object.defineProperty(ctx, key, {
  499. enumerable: true,
  500. configurable: true,
  501. get: () => setupState[key],
  502. set: NOOP
  503. })
  504. })
  505. }