component.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698
  1. import { VNode, VNodeChild, isVNode } from './vnode'
  2. import {
  3. reactive,
  4. ReactiveEffect,
  5. pauseTracking,
  6. resetTracking,
  7. shallowReadonly
  8. } from '@vue/reactivity'
  9. import {
  10. CreateComponentPublicInstance,
  11. ComponentPublicInstance,
  12. PublicInstanceProxyHandlers,
  13. RuntimeCompiledPublicInstanceProxyHandlers,
  14. createRenderContext,
  15. exposePropsOnRenderContext,
  16. exposeSetupStateOnRenderContext
  17. } from './componentProxy'
  18. import {
  19. ComponentPropsOptions,
  20. NormalizedPropsOptions,
  21. initProps
  22. } from './componentProps'
  23. import { Slots, initSlots, InternalSlots } from './componentSlots'
  24. import { warn } from './warning'
  25. import { ErrorCodes, callWithErrorHandling } from './errorHandling'
  26. import { AppContext, createAppContext, AppConfig } from './apiCreateApp'
  27. import { Directive, validateDirectiveName } from './directives'
  28. import { applyOptions, ComponentOptions } from './componentOptions'
  29. import {
  30. EmitsOptions,
  31. ObjectEmitsOptions,
  32. EmitFn,
  33. emit
  34. } from './componentEmits'
  35. import {
  36. EMPTY_OBJ,
  37. isFunction,
  38. NOOP,
  39. isObject,
  40. NO,
  41. makeMap,
  42. isPromise,
  43. ShapeFlags
  44. } from '@vue/shared'
  45. import { SuspenseBoundary } from './components/Suspense'
  46. import { CompilerOptions } from '@vue/compiler-core'
  47. import {
  48. currentRenderingInstance,
  49. markAttrsAccessed
  50. } from './componentRenderUtils'
  51. import { startMeasure, endMeasure } from './profiling'
  52. export type Data = { [key: string]: unknown }
  53. // Note: can't mark this whole interface internal because some public interfaces
  54. // extend it.
  55. export interface ComponentInternalOptions {
  56. /**
  57. * @internal
  58. */
  59. __props?: NormalizedPropsOptions | []
  60. /**
  61. * @internal
  62. */
  63. __scopeId?: string
  64. /**
  65. * @internal
  66. */
  67. __cssModules?: Data
  68. /**
  69. * @internal
  70. */
  71. __hmrId?: string
  72. /**
  73. * This one should be exposed so that devtools can make use of it
  74. */
  75. __file?: string
  76. }
  77. export interface FunctionalComponent<
  78. P = {},
  79. E extends EmitsOptions = Record<string, any>
  80. > extends ComponentInternalOptions {
  81. // use of any here is intentional so it can be a valid JSX Element constructor
  82. (props: P, ctx: SetupContext<E>): any
  83. props?: ComponentPropsOptions<P>
  84. emits?: E | (keyof E)[]
  85. inheritAttrs?: boolean
  86. inheritRef?: boolean
  87. displayName?: string
  88. }
  89. export interface ClassComponent {
  90. new (...args: any[]): ComponentPublicInstance<any, any, any, any, any>
  91. __vccOpts: ComponentOptions
  92. }
  93. export type Component = ComponentOptions | FunctionalComponent<any>
  94. // A type used in public APIs where a component type is expected.
  95. // The constructor type is an artificial type returned by defineComponent().
  96. export type PublicAPIComponent =
  97. | Component
  98. | {
  99. new (...args: any[]): CreateComponentPublicInstance<
  100. any,
  101. any,
  102. any,
  103. any,
  104. any
  105. >
  106. }
  107. export { ComponentOptions }
  108. type LifecycleHook = Function[] | null
  109. export const enum LifecycleHooks {
  110. BEFORE_CREATE = 'bc',
  111. CREATED = 'c',
  112. BEFORE_MOUNT = 'bm',
  113. MOUNTED = 'm',
  114. BEFORE_UPDATE = 'bu',
  115. UPDATED = 'u',
  116. BEFORE_UNMOUNT = 'bum',
  117. UNMOUNTED = 'um',
  118. DEACTIVATED = 'da',
  119. ACTIVATED = 'a',
  120. RENDER_TRIGGERED = 'rtg',
  121. RENDER_TRACKED = 'rtc',
  122. ERROR_CAPTURED = 'ec'
  123. }
  124. export interface SetupContext<E = ObjectEmitsOptions> {
  125. attrs: Data
  126. slots: Slots
  127. emit: EmitFn<E>
  128. }
  129. /**
  130. * @internal
  131. */
  132. export type InternalRenderFunction = {
  133. (
  134. ctx: ComponentPublicInstance,
  135. cache: ComponentInternalInstance['renderCache']
  136. ): VNodeChild
  137. _rc?: boolean // isRuntimeCompiled
  138. }
  139. /**
  140. * We expose a subset of properties on the internal instance as they are
  141. * useful for advanced external libraries and tools.
  142. */
  143. export interface ComponentInternalInstance {
  144. uid: number
  145. type: Component
  146. parent: ComponentInternalInstance | null
  147. root: ComponentInternalInstance
  148. appContext: AppContext
  149. /**
  150. * Vnode representing this component in its parent's vdom tree
  151. */
  152. vnode: VNode
  153. /**
  154. * The pending new vnode from parent updates
  155. * @internal
  156. */
  157. next: VNode | null
  158. /**
  159. * Root vnode of this component's own vdom tree
  160. */
  161. subTree: VNode
  162. /**
  163. * The reactive effect for rendering and patching the component. Callable.
  164. */
  165. update: ReactiveEffect
  166. /**
  167. * The render function that returns vdom tree.
  168. * @internal
  169. */
  170. render: InternalRenderFunction | null
  171. /**
  172. * Object containing values this component provides for its descendents
  173. * @internal
  174. */
  175. provides: Data
  176. /**
  177. * Tracking reactive effects (e.g. watchers) associated with this component
  178. * so that they can be automatically stopped on component unmount
  179. * @internal
  180. */
  181. effects: ReactiveEffect[] | null
  182. /**
  183. * cache for proxy access type to avoid hasOwnProperty calls
  184. * @internal
  185. */
  186. accessCache: Data | null
  187. /**
  188. * cache for render function values that rely on _ctx but won't need updates
  189. * after initialized (e.g. inline handlers)
  190. * @internal
  191. */
  192. renderCache: (Function | VNode)[]
  193. /**
  194. * Asset hashes that prototypally inherits app-level asset hashes for fast
  195. * resolution
  196. * @internal
  197. */
  198. components: Record<string, Component>
  199. /**
  200. * @internal
  201. */
  202. directives: Record<string, Directive>
  203. // the rest are only for stateful components ---------------------------------
  204. // main proxy that serves as the public instance (`this`)
  205. proxy: ComponentPublicInstance | null
  206. /**
  207. * alternative proxy used only for runtime-compiled render functions using
  208. * `with` block
  209. * @internal
  210. */
  211. withProxy: ComponentPublicInstance | null
  212. /**
  213. * This is the target for the public instance proxy. It also holds properties
  214. * injected by user options (computed, methods etc.) and user-attached
  215. * custom properties (via `this.x = ...`)
  216. * @internal
  217. */
  218. ctx: Data
  219. // internal state
  220. data: Data
  221. props: Data
  222. attrs: Data
  223. slots: InternalSlots
  224. refs: Data
  225. emit: EmitFn
  226. /**
  227. * setup related
  228. * @internal
  229. */
  230. setupState: Data
  231. /**
  232. * @internal
  233. */
  234. setupContext: SetupContext | null
  235. /**
  236. * suspense related
  237. * @internal
  238. */
  239. suspense: SuspenseBoundary | null
  240. /**
  241. * @internal
  242. */
  243. asyncDep: Promise<any> | null
  244. /**
  245. * @internal
  246. */
  247. asyncResolved: boolean
  248. // lifecycle
  249. isMounted: boolean
  250. isUnmounted: boolean
  251. isDeactivated: boolean
  252. /**
  253. * @internal
  254. */
  255. [LifecycleHooks.BEFORE_CREATE]: LifecycleHook
  256. /**
  257. * @internal
  258. */
  259. [LifecycleHooks.CREATED]: LifecycleHook
  260. /**
  261. * @internal
  262. */
  263. [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook
  264. /**
  265. * @internal
  266. */
  267. [LifecycleHooks.MOUNTED]: LifecycleHook
  268. /**
  269. * @internal
  270. */
  271. [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook
  272. /**
  273. * @internal
  274. */
  275. [LifecycleHooks.UPDATED]: LifecycleHook
  276. /**
  277. * @internal
  278. */
  279. [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook
  280. /**
  281. * @internal
  282. */
  283. [LifecycleHooks.UNMOUNTED]: LifecycleHook
  284. /**
  285. * @internal
  286. */
  287. [LifecycleHooks.RENDER_TRACKED]: LifecycleHook
  288. /**
  289. * @internal
  290. */
  291. [LifecycleHooks.RENDER_TRIGGERED]: LifecycleHook
  292. /**
  293. * @internal
  294. */
  295. [LifecycleHooks.ACTIVATED]: LifecycleHook
  296. /**
  297. * @internal
  298. */
  299. [LifecycleHooks.DEACTIVATED]: LifecycleHook
  300. /**
  301. * @internal
  302. */
  303. [LifecycleHooks.ERROR_CAPTURED]: LifecycleHook
  304. }
  305. const emptyAppContext = createAppContext()
  306. let uid = 0
  307. export function createComponentInstance(
  308. vnode: VNode,
  309. parent: ComponentInternalInstance | null,
  310. suspense: SuspenseBoundary | null
  311. ) {
  312. // inherit parent app context - or - if root, adopt from root vnode
  313. const appContext =
  314. (parent ? parent.appContext : vnode.appContext) || emptyAppContext
  315. const instance: ComponentInternalInstance = {
  316. uid: uid++,
  317. vnode,
  318. parent,
  319. appContext,
  320. type: vnode.type as Component,
  321. root: null!, // to be immediately set
  322. next: null,
  323. subTree: null!, // will be set synchronously right after creation
  324. update: null!, // will be set synchronously right after creation
  325. render: null,
  326. proxy: null,
  327. withProxy: null,
  328. effects: null,
  329. provides: parent ? parent.provides : Object.create(appContext.provides),
  330. accessCache: null!,
  331. renderCache: [],
  332. // state
  333. ctx: EMPTY_OBJ,
  334. data: EMPTY_OBJ,
  335. props: EMPTY_OBJ,
  336. attrs: EMPTY_OBJ,
  337. slots: EMPTY_OBJ,
  338. refs: EMPTY_OBJ,
  339. setupState: EMPTY_OBJ,
  340. setupContext: null,
  341. // per-instance asset storage (mutable during options resolution)
  342. components: Object.create(appContext.components),
  343. directives: Object.create(appContext.directives),
  344. // suspense related
  345. suspense,
  346. asyncDep: null,
  347. asyncResolved: false,
  348. // lifecycle hooks
  349. // not using enums here because it results in computed properties
  350. isMounted: false,
  351. isUnmounted: false,
  352. isDeactivated: false,
  353. bc: null,
  354. c: null,
  355. bm: null,
  356. m: null,
  357. bu: null,
  358. u: null,
  359. um: null,
  360. bum: null,
  361. da: null,
  362. a: null,
  363. rtg: null,
  364. rtc: null,
  365. ec: null,
  366. emit: null as any // to be set immediately
  367. }
  368. if (__DEV__) {
  369. instance.ctx = createRenderContext(instance)
  370. } else {
  371. instance.ctx = { _: instance }
  372. }
  373. instance.root = parent ? parent.root : instance
  374. instance.emit = emit.bind(null, instance)
  375. return instance
  376. }
  377. export let currentInstance: ComponentInternalInstance | null = null
  378. export const getCurrentInstance: () => ComponentInternalInstance | null = () =>
  379. currentInstance || currentRenderingInstance
  380. export const setCurrentInstance = (
  381. instance: ComponentInternalInstance | null
  382. ) => {
  383. currentInstance = instance
  384. }
  385. const isBuiltInTag = /*#__PURE__*/ makeMap('slot,component')
  386. export function validateComponentName(name: string, config: AppConfig) {
  387. const appIsNativeTag = config.isNativeTag || NO
  388. if (isBuiltInTag(name) || appIsNativeTag(name)) {
  389. warn(
  390. 'Do not use built-in or reserved HTML elements as component id: ' + name
  391. )
  392. }
  393. }
  394. export let isInSSRComponentSetup = false
  395. export function setupComponent(
  396. instance: ComponentInternalInstance,
  397. isSSR = false
  398. ) {
  399. isInSSRComponentSetup = isSSR
  400. const { props, children, shapeFlag } = instance.vnode
  401. const isStateful = shapeFlag & ShapeFlags.STATEFUL_COMPONENT
  402. initProps(instance, props, isStateful, isSSR)
  403. initSlots(instance, children)
  404. const setupResult = isStateful
  405. ? setupStatefulComponent(instance, isSSR)
  406. : undefined
  407. isInSSRComponentSetup = false
  408. return setupResult
  409. }
  410. function setupStatefulComponent(
  411. instance: ComponentInternalInstance,
  412. isSSR: boolean
  413. ) {
  414. const Component = instance.type as ComponentOptions
  415. if (__DEV__) {
  416. if (Component.name) {
  417. validateComponentName(Component.name, instance.appContext.config)
  418. }
  419. if (Component.components) {
  420. const names = Object.keys(Component.components)
  421. for (let i = 0; i < names.length; i++) {
  422. validateComponentName(names[i], instance.appContext.config)
  423. }
  424. }
  425. if (Component.directives) {
  426. const names = Object.keys(Component.directives)
  427. for (let i = 0; i < names.length; i++) {
  428. validateDirectiveName(names[i])
  429. }
  430. }
  431. }
  432. // 0. create render proxy property access cache
  433. instance.accessCache = {}
  434. // 1. create public instance / render proxy
  435. // also mark it raw so it's never observed
  436. instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers)
  437. if (__DEV__) {
  438. exposePropsOnRenderContext(instance)
  439. }
  440. // 2. call setup()
  441. const { setup } = Component
  442. if (setup) {
  443. const setupContext = (instance.setupContext =
  444. setup.length > 1 ? createSetupContext(instance) : null)
  445. currentInstance = instance
  446. pauseTracking()
  447. const setupResult = callWithErrorHandling(
  448. setup,
  449. instance,
  450. ErrorCodes.SETUP_FUNCTION,
  451. [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
  452. )
  453. resetTracking()
  454. currentInstance = null
  455. if (isPromise(setupResult)) {
  456. if (isSSR) {
  457. // return the promise so server-renderer can wait on it
  458. return setupResult.then((resolvedResult: unknown) => {
  459. handleSetupResult(instance, resolvedResult, isSSR)
  460. })
  461. } else if (__FEATURE_SUSPENSE__) {
  462. // async setup returned Promise.
  463. // bail here and wait for re-entry.
  464. instance.asyncDep = setupResult
  465. } else if (__DEV__) {
  466. warn(
  467. `setup() returned a Promise, but the version of Vue you are using ` +
  468. `does not support it yet.`
  469. )
  470. }
  471. } else {
  472. handleSetupResult(instance, setupResult, isSSR)
  473. }
  474. } else {
  475. finishComponentSetup(instance, isSSR)
  476. }
  477. }
  478. export function handleSetupResult(
  479. instance: ComponentInternalInstance,
  480. setupResult: unknown,
  481. isSSR: boolean
  482. ) {
  483. if (isFunction(setupResult)) {
  484. // setup returned an inline render function
  485. instance.render = setupResult as InternalRenderFunction
  486. } else if (isObject(setupResult)) {
  487. if (__DEV__ && isVNode(setupResult)) {
  488. warn(
  489. `setup() should not return VNodes directly - ` +
  490. `return a render function instead.`
  491. )
  492. }
  493. // setup returned bindings.
  494. // assuming a render function compiled from template is present.
  495. instance.setupState = reactive(setupResult)
  496. if (__DEV__) {
  497. exposeSetupStateOnRenderContext(instance)
  498. }
  499. } else if (__DEV__ && setupResult !== undefined) {
  500. warn(
  501. `setup() should return an object. Received: ${
  502. setupResult === null ? 'null' : typeof setupResult
  503. }`
  504. )
  505. }
  506. finishComponentSetup(instance, isSSR)
  507. }
  508. type CompileFunction = (
  509. template: string | object,
  510. options?: CompilerOptions
  511. ) => InternalRenderFunction
  512. let compile: CompileFunction | undefined
  513. /**
  514. * For runtime-dom to register the compiler.
  515. * Note the exported method uses any to avoid d.ts relying on the compiler types.
  516. */
  517. export function registerRuntimeCompiler(_compile: any) {
  518. compile = _compile
  519. }
  520. function finishComponentSetup(
  521. instance: ComponentInternalInstance,
  522. isSSR: boolean
  523. ) {
  524. const Component = instance.type as ComponentOptions
  525. // template / render function normalization
  526. if (__NODE_JS__ && isSSR) {
  527. if (Component.render) {
  528. instance.render = Component.render as InternalRenderFunction
  529. }
  530. } else if (!instance.render) {
  531. if (compile && Component.template && !Component.render) {
  532. if (__DEV__) {
  533. startMeasure(instance, `compile`)
  534. }
  535. Component.render = compile(Component.template, {
  536. isCustomElement: instance.appContext.config.isCustomElement || NO
  537. })
  538. if (__DEV__) {
  539. endMeasure(instance, `compile`)
  540. }
  541. // mark the function as runtime compiled
  542. ;(Component.render as InternalRenderFunction)._rc = true
  543. }
  544. if (__DEV__ && !Component.render) {
  545. /* istanbul ignore if */
  546. if (!compile && Component.template) {
  547. warn(
  548. `Component provided template option but ` +
  549. `runtime compilation is not supported in this build of Vue.` +
  550. (__ESM_BUNDLER__
  551. ? ` Configure your bundler to alias "vue" to "vue/dist/vue.esm-bundler.js".`
  552. : __ESM_BROWSER__
  553. ? ` Use "vue.esm-browser.js" instead.`
  554. : __GLOBAL__
  555. ? ` Use "vue.global.js" instead.`
  556. : ``) /* should not happen */
  557. )
  558. } else {
  559. warn(`Component is missing template or render function.`)
  560. }
  561. }
  562. instance.render = (Component.render || NOOP) as InternalRenderFunction
  563. // for runtime-compiled render functions using `with` blocks, the render
  564. // proxy used needs a different `has` handler which is more performant and
  565. // also only allows a whitelist of globals to fallthrough.
  566. if (instance.render._rc) {
  567. instance.withProxy = new Proxy(
  568. instance.ctx,
  569. RuntimeCompiledPublicInstanceProxyHandlers
  570. )
  571. }
  572. }
  573. // support for 2.x options
  574. if (__FEATURE_OPTIONS__) {
  575. currentInstance = instance
  576. applyOptions(instance, Component)
  577. currentInstance = null
  578. }
  579. }
  580. const attrHandlers: ProxyHandler<Data> = {
  581. get: (target, key: string) => {
  582. if (__DEV__) {
  583. markAttrsAccessed()
  584. }
  585. return target[key]
  586. },
  587. set: () => {
  588. warn(`setupContext.attrs is readonly.`)
  589. return false
  590. },
  591. deleteProperty: () => {
  592. warn(`setupContext.attrs is readonly.`)
  593. return false
  594. }
  595. }
  596. function createSetupContext(instance: ComponentInternalInstance): SetupContext {
  597. if (__DEV__) {
  598. // We use getters in dev in case libs like test-utils overwrite instance
  599. // properties (overwrites should not be done in prod)
  600. return Object.freeze({
  601. get attrs() {
  602. return new Proxy(instance.attrs, attrHandlers)
  603. },
  604. get slots() {
  605. return shallowReadonly(instance.slots)
  606. },
  607. get emit() {
  608. return (event: string, ...args: any[]) => instance.emit(event, ...args)
  609. }
  610. })
  611. } else {
  612. return {
  613. attrs: instance.attrs,
  614. slots: instance.slots,
  615. emit: instance.emit
  616. }
  617. }
  618. }
  619. // record effects created during a component's setup() so that they can be
  620. // stopped when the component unmounts
  621. export function recordInstanceBoundEffect(effect: ReactiveEffect) {
  622. if (currentInstance) {
  623. ;(currentInstance.effects || (currentInstance.effects = [])).push(effect)
  624. }
  625. }
  626. const classifyRE = /(?:^|[-_])(\w)/g
  627. const classify = (str: string): string =>
  628. str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '')
  629. export function formatComponentName(
  630. Component: Component,
  631. isRoot = false
  632. ): string {
  633. let name = isFunction(Component)
  634. ? Component.displayName || Component.name
  635. : Component.name
  636. if (!name && Component.__file) {
  637. const match = Component.__file.match(/([^/\\]+)\.vue$/)
  638. if (match) {
  639. name = match[1]
  640. }
  641. }
  642. return name ? classify(name) : isRoot ? `App` : `Anonymous`
  643. }