component.ts 15 KB

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