component.ts 17 KB

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