component.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  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. export interface SFCInternalOptions {
  49. __scopeId?: string
  50. __cssModules?: Data
  51. __hmrId?: string
  52. __hmrUpdated?: boolean
  53. __file?: string
  54. }
  55. export interface FunctionalComponent<
  56. P = {},
  57. E extends EmitsOptions = Record<string, any>
  58. > extends SFCInternalOptions {
  59. (props: P, ctx: SetupContext<E>): any
  60. props?: ComponentPropsOptions<P>
  61. emits?: E | (keyof E)[]
  62. inheritAttrs?: boolean
  63. displayName?: string
  64. }
  65. export interface ClassComponent {
  66. new (...args: any[]): ComponentPublicInstance<any, any, any, any, any>
  67. __vccOpts: ComponentOptions
  68. }
  69. export type Component = ComponentOptions | FunctionalComponent<any>
  70. // A type used in public APIs where a component type is expected.
  71. // The constructor type is an artificial type returned by defineComponent().
  72. export type PublicAPIComponent =
  73. | Component
  74. | { new (...args: any[]): ComponentPublicInstance<any, any, any, any, any> }
  75. export { ComponentOptions }
  76. type LifecycleHook = Function[] | null
  77. export const enum LifecycleHooks {
  78. BEFORE_CREATE = 'bc',
  79. CREATED = 'c',
  80. BEFORE_MOUNT = 'bm',
  81. MOUNTED = 'm',
  82. BEFORE_UPDATE = 'bu',
  83. UPDATED = 'u',
  84. BEFORE_UNMOUNT = 'bum',
  85. UNMOUNTED = 'um',
  86. DEACTIVATED = 'da',
  87. ACTIVATED = 'a',
  88. RENDER_TRIGGERED = 'rtg',
  89. RENDER_TRACKED = 'rtc',
  90. ERROR_CAPTURED = 'ec'
  91. }
  92. export interface SetupContext<E = ObjectEmitsOptions> {
  93. attrs: Data
  94. slots: Slots
  95. emit: EmitFn<E>
  96. }
  97. export type RenderFunction = {
  98. (
  99. ctx: ComponentPublicInstance,
  100. cache: ComponentInternalInstance['renderCache']
  101. ): VNodeChild
  102. _rc?: boolean // isRuntimeCompiled
  103. }
  104. export interface ComponentInternalInstance {
  105. uid: number
  106. type: Component
  107. parent: ComponentInternalInstance | null
  108. appContext: AppContext
  109. root: ComponentInternalInstance
  110. vnode: VNode
  111. next: VNode | null
  112. subTree: VNode
  113. update: ReactiveEffect
  114. render: RenderFunction | null
  115. effects: ReactiveEffect[] | null
  116. provides: Data
  117. // cache for proxy access type to avoid hasOwnProperty calls
  118. accessCache: Data | null
  119. // cache for render function values that rely on _ctx but won't need updates
  120. // after initialized (e.g. inline handlers)
  121. renderCache: (Function | VNode)[]
  122. // assets for fast resolution
  123. components: Record<string, Component>
  124. directives: Record<string, Directive>
  125. // the rest are only for stateful components ---------------------------------
  126. // main proxy that serves as the public instance (`this`)
  127. proxy: ComponentPublicInstance | null
  128. // alternative proxy used only for runtime-compiled render functions using
  129. // `with` block
  130. withProxy: ComponentPublicInstance | null
  131. // This is the target for the public instance proxy. It also holds properties
  132. // injected by user options (computed, methods etc.) and user-attached
  133. // custom properties (via `this.x = ...`)
  134. ctx: Data
  135. // internal state
  136. data: Data
  137. props: Data
  138. attrs: Data
  139. slots: InternalSlots
  140. refs: Data
  141. emit: EmitFn
  142. // setup
  143. setupState: Data
  144. setupContext: SetupContext | null
  145. // suspense related
  146. suspense: SuspenseBoundary | null
  147. asyncDep: Promise<any> | null
  148. asyncResolved: boolean
  149. // lifecycle
  150. isMounted: boolean
  151. isUnmounted: boolean
  152. isDeactivated: boolean
  153. [LifecycleHooks.BEFORE_CREATE]: LifecycleHook
  154. [LifecycleHooks.CREATED]: LifecycleHook
  155. [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook
  156. [LifecycleHooks.MOUNTED]: LifecycleHook
  157. [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook
  158. [LifecycleHooks.UPDATED]: LifecycleHook
  159. [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook
  160. [LifecycleHooks.UNMOUNTED]: LifecycleHook
  161. [LifecycleHooks.RENDER_TRACKED]: LifecycleHook
  162. [LifecycleHooks.RENDER_TRIGGERED]: LifecycleHook
  163. [LifecycleHooks.ACTIVATED]: LifecycleHook
  164. [LifecycleHooks.DEACTIVATED]: LifecycleHook
  165. [LifecycleHooks.ERROR_CAPTURED]: LifecycleHook
  166. // hmr marker (dev only)
  167. renderUpdated?: boolean
  168. }
  169. const emptyAppContext = createAppContext()
  170. let uid = 0
  171. export function createComponentInstance(
  172. vnode: VNode,
  173. parent: ComponentInternalInstance | null,
  174. suspense: SuspenseBoundary | null
  175. ) {
  176. // inherit parent app context - or - if root, adopt from root vnode
  177. const appContext =
  178. (parent ? parent.appContext : vnode.appContext) || emptyAppContext
  179. const instance: ComponentInternalInstance = {
  180. uid: uid++,
  181. vnode,
  182. parent,
  183. appContext,
  184. type: vnode.type as Component,
  185. root: null!, // to be immediately set
  186. next: null,
  187. subTree: null!, // will be set synchronously right after creation
  188. update: null!, // will be set synchronously right after creation
  189. render: null,
  190. proxy: null,
  191. withProxy: null,
  192. effects: null,
  193. provides: parent ? parent.provides : Object.create(appContext.provides),
  194. accessCache: null!,
  195. renderCache: [],
  196. // state
  197. ctx: EMPTY_OBJ,
  198. data: EMPTY_OBJ,
  199. props: EMPTY_OBJ,
  200. attrs: EMPTY_OBJ,
  201. slots: EMPTY_OBJ,
  202. refs: EMPTY_OBJ,
  203. setupState: EMPTY_OBJ,
  204. setupContext: null,
  205. // per-instance asset storage (mutable during options resolution)
  206. components: Object.create(appContext.components),
  207. directives: Object.create(appContext.directives),
  208. // suspense related
  209. suspense,
  210. asyncDep: null,
  211. asyncResolved: false,
  212. // lifecycle hooks
  213. // not using enums here because it results in computed properties
  214. isMounted: false,
  215. isUnmounted: false,
  216. isDeactivated: false,
  217. bc: null,
  218. c: null,
  219. bm: null,
  220. m: null,
  221. bu: null,
  222. u: null,
  223. um: null,
  224. bum: null,
  225. da: null,
  226. a: null,
  227. rtg: null,
  228. rtc: null,
  229. ec: null,
  230. emit: null as any // to be set immediately
  231. }
  232. if (__DEV__) {
  233. instance.ctx = createRenderContext(instance)
  234. } else {
  235. instance.ctx = { _: instance }
  236. }
  237. instance.root = parent ? parent.root : instance
  238. instance.emit = emit.bind(null, instance)
  239. return instance
  240. }
  241. export let currentInstance: ComponentInternalInstance | null = null
  242. export const getCurrentInstance: () => ComponentInternalInstance | null = () =>
  243. currentInstance || currentRenderingInstance
  244. export const setCurrentInstance = (
  245. instance: ComponentInternalInstance | null
  246. ) => {
  247. currentInstance = instance
  248. }
  249. const isBuiltInTag = /*#__PURE__*/ makeMap('slot,component')
  250. export function validateComponentName(name: string, config: AppConfig) {
  251. const appIsNativeTag = config.isNativeTag || NO
  252. if (isBuiltInTag(name) || appIsNativeTag(name)) {
  253. warn(
  254. 'Do not use built-in or reserved HTML elements as component id: ' + name
  255. )
  256. }
  257. }
  258. export let isInSSRComponentSetup = false
  259. export function setupComponent(
  260. instance: ComponentInternalInstance,
  261. isSSR = false
  262. ) {
  263. isInSSRComponentSetup = isSSR
  264. const { props, children, shapeFlag } = instance.vnode
  265. const isStateful = shapeFlag & ShapeFlags.STATEFUL_COMPONENT
  266. initProps(instance, props, isStateful, isSSR)
  267. initSlots(instance, children)
  268. const setupResult = isStateful
  269. ? setupStatefulComponent(instance, isSSR)
  270. : undefined
  271. isInSSRComponentSetup = false
  272. return setupResult
  273. }
  274. function setupStatefulComponent(
  275. instance: ComponentInternalInstance,
  276. isSSR: boolean
  277. ) {
  278. const Component = instance.type as ComponentOptions
  279. if (__DEV__) {
  280. if (Component.name) {
  281. validateComponentName(Component.name, instance.appContext.config)
  282. }
  283. if (Component.components) {
  284. const names = Object.keys(Component.components)
  285. for (let i = 0; i < names.length; i++) {
  286. validateComponentName(names[i], instance.appContext.config)
  287. }
  288. }
  289. if (Component.directives) {
  290. const names = Object.keys(Component.directives)
  291. for (let i = 0; i < names.length; i++) {
  292. validateDirectiveName(names[i])
  293. }
  294. }
  295. }
  296. // 0. create render proxy property access cache
  297. instance.accessCache = {}
  298. // 1. create public instance / render proxy
  299. instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers)
  300. if (__DEV__) {
  301. exposePropsOnRenderContext(instance)
  302. }
  303. // 2. call setup()
  304. const { setup } = Component
  305. if (setup) {
  306. const setupContext = (instance.setupContext =
  307. setup.length > 1 ? createSetupContext(instance) : null)
  308. currentInstance = instance
  309. pauseTracking()
  310. const setupResult = callWithErrorHandling(
  311. setup,
  312. instance,
  313. ErrorCodes.SETUP_FUNCTION,
  314. [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
  315. )
  316. resetTracking()
  317. currentInstance = null
  318. if (isPromise(setupResult)) {
  319. if (isSSR) {
  320. // return the promise so server-renderer can wait on it
  321. return setupResult.then((resolvedResult: unknown) => {
  322. handleSetupResult(instance, resolvedResult, isSSR)
  323. })
  324. } else if (__FEATURE_SUSPENSE__) {
  325. // async setup returned Promise.
  326. // bail here and wait for re-entry.
  327. instance.asyncDep = setupResult
  328. } else if (__DEV__) {
  329. warn(
  330. `setup() returned a Promise, but the version of Vue you are using ` +
  331. `does not support it yet.`
  332. )
  333. }
  334. } else {
  335. handleSetupResult(instance, setupResult, isSSR)
  336. }
  337. } else {
  338. finishComponentSetup(instance, isSSR)
  339. }
  340. }
  341. export function handleSetupResult(
  342. instance: ComponentInternalInstance,
  343. setupResult: unknown,
  344. isSSR: boolean
  345. ) {
  346. if (isFunction(setupResult)) {
  347. // setup returned an inline render function
  348. instance.render = setupResult as RenderFunction
  349. } else if (isObject(setupResult)) {
  350. if (__DEV__ && isVNode(setupResult)) {
  351. warn(
  352. `setup() should not return VNodes directly - ` +
  353. `return a render function instead.`
  354. )
  355. }
  356. // setup returned bindings.
  357. // assuming a render function compiled from template is present.
  358. instance.setupState = reactive(setupResult)
  359. if (__DEV__) {
  360. exposeSetupStateOnRenderContext(instance)
  361. }
  362. } else if (__DEV__ && setupResult !== undefined) {
  363. warn(
  364. `setup() should return an object. Received: ${
  365. setupResult === null ? 'null' : typeof setupResult
  366. }`
  367. )
  368. }
  369. finishComponentSetup(instance, isSSR)
  370. }
  371. type CompileFunction = (
  372. template: string | object,
  373. options?: CompilerOptions
  374. ) => RenderFunction
  375. let compile: CompileFunction | undefined
  376. // exported method uses any to avoid d.ts relying on the compiler types.
  377. export function registerRuntimeCompiler(_compile: any) {
  378. compile = _compile
  379. }
  380. function finishComponentSetup(
  381. instance: ComponentInternalInstance,
  382. isSSR: boolean
  383. ) {
  384. const Component = instance.type as ComponentOptions
  385. // template / render function normalization
  386. if (__NODE_JS__ && isSSR) {
  387. if (Component.render) {
  388. instance.render = Component.render as RenderFunction
  389. }
  390. } else if (!instance.render) {
  391. if (compile && Component.template && !Component.render) {
  392. if (__DEV__) {
  393. startMeasure(instance, `compile`)
  394. }
  395. Component.render = compile(Component.template, {
  396. isCustomElement: instance.appContext.config.isCustomElement || NO
  397. })
  398. if (__DEV__) {
  399. endMeasure(instance, `compile`)
  400. }
  401. // mark the function as runtime compiled
  402. ;(Component.render as RenderFunction)._rc = true
  403. }
  404. if (__DEV__ && !Component.render) {
  405. /* istanbul ignore if */
  406. if (!compile && Component.template) {
  407. warn(
  408. `Component provided template option but ` +
  409. `runtime compilation is not supported in this build of Vue.` +
  410. (__ESM_BUNDLER__
  411. ? ` Configure your bundler to alias "vue" to "vue/dist/vue.esm-bundler.js".`
  412. : __ESM_BROWSER__
  413. ? ` Use "vue.esm-browser.js" instead.`
  414. : __GLOBAL__
  415. ? ` Use "vue.global.js" instead.`
  416. : ``) /* should not happen */
  417. )
  418. } else {
  419. warn(`Component is missing template or render function.`)
  420. }
  421. }
  422. instance.render = (Component.render || NOOP) as RenderFunction
  423. // for runtime-compiled render functions using `with` blocks, the render
  424. // proxy used needs a different `has` handler which is more performant and
  425. // also only allows a whitelist of globals to fallthrough.
  426. if (instance.render._rc) {
  427. instance.withProxy = new Proxy(
  428. instance.ctx,
  429. RuntimeCompiledPublicInstanceProxyHandlers
  430. )
  431. }
  432. }
  433. // support for 2.x options
  434. if (__FEATURE_OPTIONS__) {
  435. currentInstance = instance
  436. applyOptions(instance, Component)
  437. currentInstance = null
  438. }
  439. }
  440. const attrHandlers: ProxyHandler<Data> = {
  441. get: (target, key: string) => {
  442. if (__DEV__) {
  443. markAttrsAccessed()
  444. }
  445. return target[key]
  446. },
  447. set: () => {
  448. warn(`setupContext.attrs is readonly.`)
  449. return false
  450. },
  451. deleteProperty: () => {
  452. warn(`setupContext.attrs is readonly.`)
  453. return false
  454. }
  455. }
  456. function createSetupContext(instance: ComponentInternalInstance): SetupContext {
  457. if (__DEV__) {
  458. // We use getters in dev in case libs like test-utils overwrite instance
  459. // properties (overwrites should not be done in prod)
  460. return Object.freeze({
  461. get attrs() {
  462. return new Proxy(instance.attrs, attrHandlers)
  463. },
  464. get slots() {
  465. return shallowReadonly(instance.slots)
  466. },
  467. get emit() {
  468. return (event: string, ...args: any[]) => instance.emit(event, ...args)
  469. }
  470. })
  471. } else {
  472. return {
  473. attrs: instance.attrs,
  474. slots: instance.slots,
  475. emit: instance.emit
  476. }
  477. }
  478. }
  479. // record effects created during a component's setup() so that they can be
  480. // stopped when the component unmounts
  481. export function recordInstanceBoundEffect(effect: ReactiveEffect) {
  482. if (currentInstance) {
  483. ;(currentInstance.effects || (currentInstance.effects = [])).push(effect)
  484. }
  485. }
  486. const classifyRE = /(?:^|[-_])(\w)/g
  487. const classify = (str: string): string =>
  488. str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '')
  489. export function formatComponentName(
  490. Component: Component,
  491. isRoot = false
  492. ): string {
  493. let name = isFunction(Component)
  494. ? Component.displayName || Component.name
  495. : Component.name
  496. if (!name && Component.__file) {
  497. const match = Component.__file.match(/([^/\\]+)\.vue$/)
  498. if (match) {
  499. name = match[1]
  500. }
  501. }
  502. return name ? classify(name) : isRoot ? `App` : `Anonymous`
  503. }