component.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  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. ComponentPublicProxyTarget,
  12. PublicInstanceProxyHandlers,
  13. RuntimeCompiledPublicInstanceProxyHandlers,
  14. createDevProxyTarget,
  15. exposePropsOnDevProxyTarget,
  16. exposeRenderContextOnDevProxyTarget
  17. } from './componentProxy'
  18. import { ComponentPropsOptions, initProps } from './componentProps'
  19. import { Slots, initSlots, InternalSlots } from './componentSlots'
  20. import { warn } from './warning'
  21. import { ErrorCodes, callWithErrorHandling } from './errorHandling'
  22. import { AppContext, createAppContext, AppConfig } from './apiCreateApp'
  23. import { Directive, validateDirectiveName } from './directives'
  24. import { applyOptions, ComponentOptions } from './componentOptions'
  25. import {
  26. EmitsOptions,
  27. ObjectEmitsOptions,
  28. EmitFn,
  29. emit
  30. } from './componentEmits'
  31. import {
  32. EMPTY_OBJ,
  33. isFunction,
  34. NOOP,
  35. isObject,
  36. NO,
  37. makeMap,
  38. isPromise,
  39. ShapeFlags
  40. } from '@vue/shared'
  41. import { SuspenseBoundary } from './components/Suspense'
  42. import { CompilerOptions } from '@vue/compiler-core'
  43. import {
  44. currentRenderingInstance,
  45. markAttrsAccessed
  46. } from './componentRenderUtils'
  47. import { startMeasure, endMeasure } from './profiling'
  48. export type Data = { [key: string]: unknown }
  49. export interface SFCInternalOptions {
  50. __scopeId?: string
  51. __cssModules?: Data
  52. __hmrId?: string
  53. __hmrUpdated?: boolean
  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. renderContext: Data
  127. data: Data
  128. props: Data
  129. attrs: Data
  130. slots: InternalSlots
  131. proxy: ComponentPublicInstance | null
  132. proxyTarget: ComponentPublicProxyTarget
  133. // alternative proxy used only for runtime-compiled render functions using
  134. // `with` block
  135. withProxy: ComponentPublicInstance | null
  136. setupContext: SetupContext | null
  137. refs: Data
  138. emit: EmitFn
  139. // suspense related
  140. suspense: SuspenseBoundary | null
  141. asyncDep: Promise<any> | null
  142. asyncResolved: boolean
  143. // storage for any extra properties
  144. sink: { [key: string]: any }
  145. // lifecycle
  146. isMounted: boolean
  147. isUnmounted: boolean
  148. isDeactivated: boolean
  149. [LifecycleHooks.BEFORE_CREATE]: LifecycleHook
  150. [LifecycleHooks.CREATED]: LifecycleHook
  151. [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook
  152. [LifecycleHooks.MOUNTED]: LifecycleHook
  153. [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook
  154. [LifecycleHooks.UPDATED]: LifecycleHook
  155. [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook
  156. [LifecycleHooks.UNMOUNTED]: LifecycleHook
  157. [LifecycleHooks.RENDER_TRACKED]: LifecycleHook
  158. [LifecycleHooks.RENDER_TRIGGERED]: LifecycleHook
  159. [LifecycleHooks.ACTIVATED]: LifecycleHook
  160. [LifecycleHooks.DEACTIVATED]: LifecycleHook
  161. [LifecycleHooks.ERROR_CAPTURED]: LifecycleHook
  162. // hmr marker (dev only)
  163. renderUpdated?: boolean
  164. }
  165. const emptyAppContext = createAppContext()
  166. let uid = 0
  167. export function createComponentInstance(
  168. vnode: VNode,
  169. parent: ComponentInternalInstance | null,
  170. suspense: SuspenseBoundary | null
  171. ) {
  172. // inherit parent app context - or - if root, adopt from root vnode
  173. const appContext =
  174. (parent ? parent.appContext : vnode.appContext) || emptyAppContext
  175. const instance: ComponentInternalInstance = {
  176. uid: uid++,
  177. vnode,
  178. parent,
  179. appContext,
  180. type: vnode.type as Component,
  181. root: null!, // to be immediately set
  182. next: null,
  183. subTree: null!, // will be set synchronously right after creation
  184. update: null!, // will be set synchronously right after creation
  185. render: null,
  186. proxy: null,
  187. proxyTarget: null!, // to be immediately set
  188. withProxy: null,
  189. setupContext: null,
  190. effects: null,
  191. provides: parent ? parent.provides : Object.create(appContext.provides),
  192. accessCache: null!,
  193. renderCache: [],
  194. // setup context properties
  195. renderContext: EMPTY_OBJ,
  196. data: EMPTY_OBJ,
  197. props: EMPTY_OBJ,
  198. attrs: EMPTY_OBJ,
  199. slots: EMPTY_OBJ,
  200. refs: EMPTY_OBJ,
  201. // per-instance asset storage (mutable during options resolution)
  202. components: Object.create(appContext.components),
  203. directives: Object.create(appContext.directives),
  204. // suspense related
  205. suspense,
  206. asyncDep: null,
  207. asyncResolved: false,
  208. // user namespace for storing whatever the user assigns to `this`
  209. // can also be used as a wildcard storage for ad-hoc injections internally
  210. sink: {},
  211. // lifecycle hooks
  212. // not using enums here because it results in computed properties
  213. isMounted: false,
  214. isUnmounted: false,
  215. isDeactivated: false,
  216. bc: null,
  217. c: null,
  218. bm: null,
  219. m: null,
  220. bu: null,
  221. u: null,
  222. um: null,
  223. bum: null,
  224. da: null,
  225. a: null,
  226. rtg: null,
  227. rtc: null,
  228. ec: null,
  229. emit: null as any // to be set immediately
  230. }
  231. if (__DEV__) {
  232. instance.proxyTarget = createDevProxyTarget(instance)
  233. } else {
  234. instance.proxyTarget = { _: instance }
  235. }
  236. instance.root = parent ? parent.root : instance
  237. instance.emit = emit.bind(null, instance)
  238. return instance
  239. }
  240. export let currentInstance: ComponentInternalInstance | null = null
  241. export const getCurrentInstance: () => ComponentInternalInstance | null = () =>
  242. currentInstance || currentRenderingInstance
  243. export const setCurrentInstance = (
  244. instance: ComponentInternalInstance | null
  245. ) => {
  246. currentInstance = instance
  247. }
  248. const isBuiltInTag = /*#__PURE__*/ makeMap('slot,component')
  249. export function validateComponentName(name: string, config: AppConfig) {
  250. const appIsNativeTag = config.isNativeTag || NO
  251. if (isBuiltInTag(name) || appIsNativeTag(name)) {
  252. warn(
  253. 'Do not use built-in or reserved HTML elements as component id: ' + name
  254. )
  255. }
  256. }
  257. export let isInSSRComponentSetup = false
  258. export function setupComponent(
  259. instance: ComponentInternalInstance,
  260. isSSR = false
  261. ) {
  262. isInSSRComponentSetup = isSSR
  263. const { props, children, shapeFlag } = instance.vnode
  264. const isStateful = shapeFlag & ShapeFlags.STATEFUL_COMPONENT
  265. initProps(instance, props, isStateful, isSSR)
  266. initSlots(instance, children)
  267. const setupResult = isStateful
  268. ? setupStatefulComponent(instance, isSSR)
  269. : undefined
  270. isInSSRComponentSetup = false
  271. return setupResult
  272. }
  273. function setupStatefulComponent(
  274. instance: ComponentInternalInstance,
  275. isSSR: boolean
  276. ) {
  277. const Component = instance.type as ComponentOptions
  278. if (__DEV__) {
  279. if (Component.name) {
  280. validateComponentName(Component.name, instance.appContext.config)
  281. }
  282. if (Component.components) {
  283. const names = Object.keys(Component.components)
  284. for (let i = 0; i < names.length; i++) {
  285. validateComponentName(names[i], instance.appContext.config)
  286. }
  287. }
  288. if (Component.directives) {
  289. const names = Object.keys(Component.directives)
  290. for (let i = 0; i < names.length; i++) {
  291. validateDirectiveName(names[i])
  292. }
  293. }
  294. }
  295. // 0. create render proxy property access cache
  296. instance.accessCache = {}
  297. // 1. create public instance / render proxy
  298. instance.proxy = new Proxy(instance.proxyTarget, PublicInstanceProxyHandlers)
  299. if (__DEV__) {
  300. exposePropsOnDevProxyTarget(instance)
  301. }
  302. // 2. call setup()
  303. const { setup } = Component
  304. if (setup) {
  305. const setupContext = (instance.setupContext =
  306. setup.length > 1 ? createSetupContext(instance) : null)
  307. currentInstance = instance
  308. pauseTracking()
  309. const setupResult = callWithErrorHandling(
  310. setup,
  311. instance,
  312. ErrorCodes.SETUP_FUNCTION,
  313. [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
  314. )
  315. resetTracking()
  316. currentInstance = null
  317. if (isPromise(setupResult)) {
  318. if (isSSR) {
  319. // return the promise so server-renderer can wait on it
  320. return setupResult.then((resolvedResult: unknown) => {
  321. handleSetupResult(instance, resolvedResult, isSSR)
  322. })
  323. } else if (__FEATURE_SUSPENSE__) {
  324. // async setup returned Promise.
  325. // bail here and wait for re-entry.
  326. instance.asyncDep = setupResult
  327. } else if (__DEV__) {
  328. warn(
  329. `setup() returned a Promise, but the version of Vue you are using ` +
  330. `does not support it yet.`
  331. )
  332. }
  333. } else {
  334. handleSetupResult(instance, setupResult, isSSR)
  335. }
  336. } else {
  337. finishComponentSetup(instance, isSSR)
  338. }
  339. }
  340. export function handleSetupResult(
  341. instance: ComponentInternalInstance,
  342. setupResult: unknown,
  343. isSSR: boolean
  344. ) {
  345. if (isFunction(setupResult)) {
  346. // setup returned an inline render function
  347. instance.render = setupResult as RenderFunction
  348. } else if (isObject(setupResult)) {
  349. if (__DEV__ && isVNode(setupResult)) {
  350. warn(
  351. `setup() should not return VNodes directly - ` +
  352. `return a render function instead.`
  353. )
  354. }
  355. // setup returned bindings.
  356. // assuming a render function compiled from template is present.
  357. instance.renderContext = reactive(setupResult)
  358. if (__DEV__) {
  359. exposeRenderContextOnDevProxyTarget(instance)
  360. }
  361. } else if (__DEV__ && setupResult !== undefined) {
  362. warn(
  363. `setup() should return an object. Received: ${
  364. setupResult === null ? 'null' : typeof setupResult
  365. }`
  366. )
  367. }
  368. finishComponentSetup(instance, isSSR)
  369. }
  370. type CompileFunction = (
  371. template: string | object,
  372. options?: CompilerOptions
  373. ) => RenderFunction
  374. let compile: CompileFunction | undefined
  375. // exported method uses any to avoid d.ts relying on the compiler types.
  376. export function registerRuntimeCompiler(_compile: any) {
  377. compile = _compile
  378. }
  379. function finishComponentSetup(
  380. instance: ComponentInternalInstance,
  381. isSSR: boolean
  382. ) {
  383. const Component = instance.type as ComponentOptions
  384. // template / render function normalization
  385. if (__NODE_JS__ && isSSR) {
  386. if (Component.render) {
  387. instance.render = Component.render as RenderFunction
  388. }
  389. } else if (!instance.render) {
  390. if (compile && Component.template && !Component.render) {
  391. if (__DEV__) {
  392. startMeasure(instance, `compile`)
  393. }
  394. Component.render = compile(Component.template, {
  395. isCustomElement: instance.appContext.config.isCustomElement || NO
  396. })
  397. if (__DEV__) {
  398. endMeasure(instance, `compile`)
  399. }
  400. // mark the function as runtime compiled
  401. ;(Component.render as RenderFunction)._rc = true
  402. }
  403. if (__DEV__ && !Component.render) {
  404. /* istanbul ignore if */
  405. if (!compile && Component.template) {
  406. warn(
  407. `Component provides template but the build of Vue you are running ` +
  408. `does not support runtime template compilation. Either use the ` +
  409. `full build or pre-compile the template using Vue CLI.`
  410. )
  411. } else {
  412. warn(`Component is missing template or render function.`)
  413. }
  414. }
  415. instance.render = (Component.render || NOOP) as RenderFunction
  416. // for runtime-compiled render functions using `with` blocks, the render
  417. // proxy used needs a different `has` handler which is more performant and
  418. // also only allows a whitelist of globals to fallthrough.
  419. if (instance.render._rc) {
  420. instance.withProxy = new Proxy(
  421. instance.proxyTarget,
  422. RuntimeCompiledPublicInstanceProxyHandlers
  423. )
  424. }
  425. }
  426. // support for 2.x options
  427. if (__FEATURE_OPTIONS__) {
  428. currentInstance = instance
  429. applyOptions(instance, Component)
  430. currentInstance = null
  431. }
  432. }
  433. const attrHandlers: ProxyHandler<Data> = {
  434. get: (target, key: string) => {
  435. markAttrsAccessed()
  436. return target[key]
  437. },
  438. set: () => {
  439. warn(`setupContext.attrs is readonly.`)
  440. return false
  441. },
  442. deleteProperty: () => {
  443. warn(`setupContext.attrs is readonly.`)
  444. return false
  445. }
  446. }
  447. function createSetupContext(instance: ComponentInternalInstance): SetupContext {
  448. if (__DEV__) {
  449. // We use getters in dev in case libs like test-utils overwrite instance
  450. // properties (overwrites should not be done in prod)
  451. return Object.freeze({
  452. get attrs() {
  453. return new Proxy(instance.attrs, attrHandlers)
  454. },
  455. get slots() {
  456. return shallowReadonly(instance.slots)
  457. },
  458. get emit() {
  459. return (event: string, ...args: any[]) => instance.emit(event, ...args)
  460. }
  461. })
  462. } else {
  463. return {
  464. attrs: instance.attrs,
  465. slots: instance.slots,
  466. emit: instance.emit
  467. }
  468. }
  469. }
  470. // record effects created during a component's setup() so that they can be
  471. // stopped when the component unmounts
  472. export function recordInstanceBoundEffect(effect: ReactiveEffect) {
  473. if (currentInstance) {
  474. ;(currentInstance.effects || (currentInstance.effects = [])).push(effect)
  475. }
  476. }
  477. const classifyRE = /(?:^|[-_])(\w)/g
  478. const classify = (str: string): string =>
  479. str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '')
  480. export function formatComponentName(
  481. Component: Component,
  482. file?: string
  483. ): string {
  484. let name = isFunction(Component)
  485. ? Component.displayName || Component.name
  486. : Component.name
  487. if (!name && file) {
  488. const match = file.match(/([^/\\]+)\.vue$/)
  489. if (match) {
  490. name = match[1]
  491. }
  492. }
  493. return name ? classify(name) : 'Anonymous'
  494. }