2
0

component.ts 18 KB

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