component.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. import { VNode, VNodeChild, isVNode } from './vnode'
  2. import {
  3. reactive,
  4. ReactiveEffect,
  5. shallowReadonly,
  6. pauseTracking,
  7. resetTracking
  8. } from '@vue/reactivity'
  9. import {
  10. PublicInstanceProxyHandlers,
  11. ComponentPublicInstance,
  12. runtimeCompiledRenderProxyHandlers
  13. } from './componentProxy'
  14. import { ComponentPropsOptions, resolveProps } from './componentProps'
  15. import { Slots, resolveSlots } from './componentSlots'
  16. import { warn } from './warning'
  17. import {
  18. ErrorCodes,
  19. callWithErrorHandling,
  20. callWithAsyncErrorHandling
  21. } from './errorHandling'
  22. import { AppContext, createAppContext, AppConfig } from './apiCreateApp'
  23. import { Directive, validateDirectiveName } from './directives'
  24. import { applyOptions, ComponentOptions } from './apiOptions'
  25. import {
  26. EMPTY_OBJ,
  27. isFunction,
  28. capitalize,
  29. NOOP,
  30. isObject,
  31. NO,
  32. makeMap,
  33. isPromise,
  34. isArray,
  35. hyphenate,
  36. ShapeFlags
  37. } from '@vue/shared'
  38. import { SuspenseBoundary } from './components/Suspense'
  39. import { CompilerOptions } from '@vue/compiler-core'
  40. import {
  41. currentRenderingInstance,
  42. markAttrsAccessed
  43. } from './componentRenderUtils'
  44. export type Data = { [key: string]: unknown }
  45. export interface SFCInternalOptions {
  46. __scopeId?: string
  47. __cssModules?: Data
  48. __hmrId?: string
  49. __hmrUpdated?: boolean
  50. }
  51. export interface FunctionalComponent<P = {}> extends SFCInternalOptions {
  52. (props: P, ctx: SetupContext): VNodeChild
  53. props?: ComponentPropsOptions<P>
  54. inheritAttrs?: boolean
  55. displayName?: string
  56. }
  57. export interface ClassComponent {
  58. new (...args: any[]): ComponentPublicInstance<any, any, any, any, any>
  59. __vccOpts: ComponentOptions
  60. }
  61. export type Component = ComponentOptions | FunctionalComponent
  62. // A type used in public APIs where a component type is expected.
  63. // The constructor type is an artificial type returned by defineComponent().
  64. export type PublicAPIComponent =
  65. | Component
  66. | { new (...args: any[]): ComponentPublicInstance<any, any, any, any, any> }
  67. export { ComponentOptions }
  68. type LifecycleHook = Function[] | null
  69. export const enum LifecycleHooks {
  70. BEFORE_CREATE = 'bc',
  71. CREATED = 'c',
  72. BEFORE_MOUNT = 'bm',
  73. MOUNTED = 'm',
  74. BEFORE_UPDATE = 'bu',
  75. UPDATED = 'u',
  76. BEFORE_UNMOUNT = 'bum',
  77. UNMOUNTED = 'um',
  78. DEACTIVATED = 'da',
  79. ACTIVATED = 'a',
  80. RENDER_TRIGGERED = 'rtg',
  81. RENDER_TRACKED = 'rtc',
  82. ERROR_CAPTURED = 'ec'
  83. }
  84. export type Emit = (event: string, ...args: unknown[]) => any[]
  85. export interface SetupContext {
  86. attrs: Data
  87. slots: Slots
  88. emit: Emit
  89. }
  90. export type RenderFunction = {
  91. (): VNodeChild
  92. _rc?: boolean // isRuntimeCompiled
  93. }
  94. export interface ComponentInternalInstance {
  95. type: FunctionalComponent | ComponentOptions
  96. parent: ComponentInternalInstance | null
  97. appContext: AppContext
  98. root: ComponentInternalInstance
  99. vnode: VNode
  100. next: VNode | null
  101. subTree: VNode
  102. update: ReactiveEffect
  103. render: RenderFunction | null
  104. effects: ReactiveEffect[] | null
  105. provides: Data
  106. // cache for proxy access type to avoid hasOwnProperty calls
  107. accessCache: Data | null
  108. // cache for render function values that rely on _ctx but won't need updates
  109. // after initialized (e.g. inline handlers)
  110. renderCache: (Function | VNode)[]
  111. // assets for fast resolution
  112. components: Record<string, Component>
  113. directives: Record<string, Directive>
  114. // the rest are only for stateful components
  115. renderContext: Data
  116. data: Data
  117. props: Data
  118. attrs: Data
  119. vnodeHooks: Data
  120. slots: Slots
  121. proxy: ComponentPublicInstance | null
  122. // alternative proxy used only for runtime-compiled render functions using
  123. // `with` block
  124. withProxy: ComponentPublicInstance | null
  125. propsProxy: Data | null
  126. setupContext: SetupContext | null
  127. refs: Data
  128. emit: Emit
  129. // suspense related
  130. asyncDep: Promise<any> | null
  131. asyncResult: unknown
  132. asyncResolved: boolean
  133. // storage for any extra properties
  134. sink: { [key: string]: any }
  135. // lifecycle
  136. isMounted: boolean
  137. isUnmounted: boolean
  138. isDeactivated: boolean
  139. [LifecycleHooks.BEFORE_CREATE]: LifecycleHook
  140. [LifecycleHooks.CREATED]: LifecycleHook
  141. [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook
  142. [LifecycleHooks.MOUNTED]: LifecycleHook
  143. [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook
  144. [LifecycleHooks.UPDATED]: LifecycleHook
  145. [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook
  146. [LifecycleHooks.UNMOUNTED]: LifecycleHook
  147. [LifecycleHooks.RENDER_TRACKED]: LifecycleHook
  148. [LifecycleHooks.RENDER_TRIGGERED]: LifecycleHook
  149. [LifecycleHooks.ACTIVATED]: LifecycleHook
  150. [LifecycleHooks.DEACTIVATED]: LifecycleHook
  151. [LifecycleHooks.ERROR_CAPTURED]: LifecycleHook
  152. // hmr marker (dev only)
  153. renderUpdated?: boolean
  154. }
  155. const emptyAppContext = createAppContext()
  156. export function createComponentInstance(
  157. vnode: VNode,
  158. parent: ComponentInternalInstance | null
  159. ) {
  160. // inherit parent app context - or - if root, adopt from root vnode
  161. const appContext =
  162. (parent ? parent.appContext : vnode.appContext) || emptyAppContext
  163. const instance: ComponentInternalInstance = {
  164. vnode,
  165. parent,
  166. appContext,
  167. type: vnode.type as Component,
  168. root: null!, // set later so it can point to itself
  169. next: null,
  170. subTree: null!, // will be set synchronously right after creation
  171. update: null!, // will be set synchronously right after creation
  172. render: null,
  173. proxy: null,
  174. withProxy: null,
  175. propsProxy: null,
  176. setupContext: null,
  177. effects: null,
  178. provides: parent ? parent.provides : Object.create(appContext.provides),
  179. accessCache: null!,
  180. renderCache: [],
  181. // setup context properties
  182. renderContext: EMPTY_OBJ,
  183. data: EMPTY_OBJ,
  184. props: EMPTY_OBJ,
  185. attrs: EMPTY_OBJ,
  186. vnodeHooks: EMPTY_OBJ,
  187. slots: EMPTY_OBJ,
  188. refs: EMPTY_OBJ,
  189. // per-instance asset storage (mutable during options resolution)
  190. components: Object.create(appContext.components),
  191. directives: Object.create(appContext.directives),
  192. // async dependency management
  193. asyncDep: null,
  194. asyncResult: null,
  195. asyncResolved: false,
  196. // user namespace for storing whatever the user assigns to `this`
  197. // can also be used as a wildcard storage for ad-hoc injections internally
  198. sink: {},
  199. // lifecycle hooks
  200. // not using enums here because it results in computed properties
  201. isMounted: false,
  202. isUnmounted: false,
  203. isDeactivated: false,
  204. bc: null,
  205. c: null,
  206. bm: null,
  207. m: null,
  208. bu: null,
  209. u: null,
  210. um: null,
  211. bum: null,
  212. da: null,
  213. a: null,
  214. rtg: null,
  215. rtc: null,
  216. ec: null,
  217. emit: (event, ...args): any[] => {
  218. const props = instance.vnode.props || EMPTY_OBJ
  219. let handler = props[`on${event}`] || props[`on${capitalize(event)}`]
  220. if (!handler && event.indexOf('update:') === 0) {
  221. event = hyphenate(event)
  222. handler = props[`on${event}`] || props[`on${capitalize(event)}`]
  223. }
  224. if (handler) {
  225. const res = callWithAsyncErrorHandling(
  226. handler,
  227. instance,
  228. ErrorCodes.COMPONENT_EVENT_HANDLER,
  229. args
  230. )
  231. return isArray(res) ? res : [res]
  232. } else {
  233. return []
  234. }
  235. }
  236. }
  237. instance.root = parent ? parent.root : instance
  238. return instance
  239. }
  240. export let currentInstance: ComponentInternalInstance | null = null
  241. export let currentSuspense: SuspenseBoundary | 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. parentSuspense: SuspenseBoundary | null,
  262. isSSR = false
  263. ) {
  264. isInSSRComponentSetup = isSSR
  265. const propsOptions = instance.type.props
  266. const { props, children, shapeFlag } = instance.vnode
  267. resolveProps(instance, props, propsOptions)
  268. resolveSlots(instance, children)
  269. // setup stateful logic
  270. let setupResult
  271. if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
  272. setupResult = setupStatefulComponent(instance, parentSuspense, isSSR)
  273. }
  274. isInSSRComponentSetup = false
  275. return setupResult
  276. }
  277. function setupStatefulComponent(
  278. instance: ComponentInternalInstance,
  279. parentSuspense: SuspenseBoundary | null,
  280. isSSR: boolean
  281. ) {
  282. const Component = instance.type as ComponentOptions
  283. if (__DEV__) {
  284. if (Component.name) {
  285. validateComponentName(Component.name, instance.appContext.config)
  286. }
  287. if (Component.components) {
  288. const names = Object.keys(Component.components)
  289. for (let i = 0; i < names.length; i++) {
  290. validateComponentName(names[i], instance.appContext.config)
  291. }
  292. }
  293. if (Component.directives) {
  294. const names = Object.keys(Component.directives)
  295. for (let i = 0; i < names.length; i++) {
  296. validateDirectiveName(names[i])
  297. }
  298. }
  299. }
  300. // 0. create render proxy property access cache
  301. instance.accessCache = {}
  302. // 1. create public instance / render proxy
  303. instance.proxy = new Proxy(instance, PublicInstanceProxyHandlers)
  304. // 2. create props proxy
  305. // the propsProxy is a reactive AND readonly proxy to the actual props.
  306. // it will be updated in resolveProps() on updates before render
  307. const propsProxy = (instance.propsProxy = isSSR
  308. ? instance.props
  309. : shallowReadonly(instance.props))
  310. // 3. call setup()
  311. const { setup } = Component
  312. if (setup) {
  313. const setupContext = (instance.setupContext =
  314. setup.length > 1 ? createSetupContext(instance) : null)
  315. currentInstance = instance
  316. currentSuspense = parentSuspense
  317. pauseTracking()
  318. const setupResult = callWithErrorHandling(
  319. setup,
  320. instance,
  321. ErrorCodes.SETUP_FUNCTION,
  322. [propsProxy, setupContext]
  323. )
  324. resetTracking()
  325. currentInstance = null
  326. currentSuspense = null
  327. if (isPromise(setupResult)) {
  328. if (isSSR) {
  329. // return the promise so server-renderer can wait on it
  330. return setupResult.then(resolvedResult => {
  331. handleSetupResult(instance, resolvedResult, parentSuspense, isSSR)
  332. })
  333. } else if (__FEATURE_SUSPENSE__) {
  334. // async setup returned Promise.
  335. // bail here and wait for re-entry.
  336. instance.asyncDep = setupResult
  337. } else if (__DEV__) {
  338. warn(
  339. `setup() returned a Promise, but the version of Vue you are using ` +
  340. `does not support it yet.`
  341. )
  342. }
  343. } else {
  344. handleSetupResult(instance, setupResult, parentSuspense, isSSR)
  345. }
  346. } else {
  347. finishComponentSetup(instance, parentSuspense, isSSR)
  348. }
  349. }
  350. export function handleSetupResult(
  351. instance: ComponentInternalInstance,
  352. setupResult: unknown,
  353. parentSuspense: SuspenseBoundary | null,
  354. isSSR: boolean
  355. ) {
  356. if (isFunction(setupResult)) {
  357. // setup returned an inline render function
  358. instance.render = setupResult as RenderFunction
  359. } else if (isObject(setupResult)) {
  360. if (__DEV__ && isVNode(setupResult)) {
  361. warn(
  362. `setup() should not return VNodes directly - ` +
  363. `return a render function instead.`
  364. )
  365. }
  366. // setup returned bindings.
  367. // assuming a render function compiled from template is present.
  368. instance.renderContext = reactive(setupResult)
  369. } else if (__DEV__ && setupResult !== undefined) {
  370. warn(
  371. `setup() should return an object. Received: ${
  372. setupResult === null ? 'null' : typeof setupResult
  373. }`
  374. )
  375. }
  376. finishComponentSetup(instance, parentSuspense, isSSR)
  377. }
  378. type CompileFunction = (
  379. template: string | object,
  380. options?: CompilerOptions
  381. ) => RenderFunction
  382. let compile: CompileFunction | undefined
  383. // exported method uses any to avoid d.ts relying on the compiler types.
  384. export function registerRuntimeCompiler(_compile: any) {
  385. compile = _compile
  386. }
  387. function finishComponentSetup(
  388. instance: ComponentInternalInstance,
  389. parentSuspense: SuspenseBoundary | null,
  390. isSSR: boolean
  391. ) {
  392. const Component = instance.type as ComponentOptions
  393. // template / render function normalization
  394. if (__NODE_JS__ && isSSR) {
  395. if (Component.render) {
  396. instance.render = Component.render as RenderFunction
  397. }
  398. } else if (!instance.render) {
  399. if (compile && Component.template && !Component.render) {
  400. Component.render = compile(Component.template, {
  401. isCustomElement: instance.appContext.config.isCustomElement || NO
  402. })
  403. // mark the function as runtime compiled
  404. ;(Component.render as RenderFunction)._rc = true
  405. }
  406. if (__DEV__ && !Component.render) {
  407. /* istanbul ignore if */
  408. if (!compile && Component.template) {
  409. warn(
  410. `Component provides template but the build of Vue you are running ` +
  411. `does not support runtime template compilation. Either use the ` +
  412. `full build or pre-compile the template using Vue CLI.`
  413. )
  414. } else {
  415. warn(`Component is missing template or render function.`)
  416. }
  417. }
  418. instance.render = (Component.render || NOOP) as RenderFunction
  419. // for runtime-compiled render functions using `with` blocks, the render
  420. // proxy used needs a different `has` handler which is more performant and
  421. // also only allows a whitelist of globals to fallthrough.
  422. if (instance.render._rc) {
  423. instance.withProxy = new Proxy(
  424. instance,
  425. runtimeCompiledRenderProxyHandlers
  426. )
  427. }
  428. }
  429. // support for 2.x options
  430. if (__FEATURE_OPTIONS__) {
  431. currentInstance = instance
  432. currentSuspense = parentSuspense
  433. applyOptions(instance, Component)
  434. currentInstance = null
  435. currentSuspense = null
  436. }
  437. }
  438. // used to identify a setup context proxy
  439. export const SetupProxySymbol = Symbol()
  440. const SetupProxyHandlers: { [key: string]: ProxyHandler<any> } = {}
  441. ;['attrs', 'slots'].forEach((type: string) => {
  442. SetupProxyHandlers[type] = {
  443. get: (instance, key) => {
  444. if (__DEV__) {
  445. markAttrsAccessed()
  446. }
  447. return instance[type][key]
  448. },
  449. has: (instance, key) => key === SetupProxySymbol || key in instance[type],
  450. ownKeys: instance => Reflect.ownKeys(instance[type]),
  451. // this is necessary for ownKeys to work properly
  452. getOwnPropertyDescriptor: (instance, key) =>
  453. Reflect.getOwnPropertyDescriptor(instance[type], key),
  454. set: () => false,
  455. deleteProperty: () => false
  456. }
  457. })
  458. function createSetupContext(instance: ComponentInternalInstance): SetupContext {
  459. const context = {
  460. // attrs & slots are non-reactive, but they need to always expose
  461. // the latest values (instance.xxx may get replaced during updates) so we
  462. // need to expose them through a proxy
  463. attrs: new Proxy(instance, SetupProxyHandlers.attrs),
  464. slots: new Proxy(instance, SetupProxyHandlers.slots),
  465. get emit() {
  466. return instance.emit
  467. }
  468. }
  469. return __DEV__ ? Object.freeze(context) : context
  470. }
  471. // record effects created during a component's setup() so that they can be
  472. // stopped when the component unmounts
  473. export function recordInstanceBoundEffect(effect: ReactiveEffect) {
  474. if (currentInstance) {
  475. ;(currentInstance.effects || (currentInstance.effects = [])).push(effect)
  476. }
  477. }