component.ts 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. import { VNode, VNodeChild } from './vnode'
  2. import { ReactiveEffect, reactive, readonly } from '@vue/reactivity'
  3. import {
  4. PublicInstanceProxyHandlers,
  5. ComponentPublicInstance
  6. } from './componentPublicInstanceProxy'
  7. import { ComponentPropsOptions } from './componentProps'
  8. import { Slots } from './componentSlots'
  9. import { warn } from './warning'
  10. import {
  11. ErrorCodes,
  12. callWithErrorHandling,
  13. callWithAsyncErrorHandling
  14. } from './errorHandling'
  15. import { AppContext, createAppContext } from './apiApp'
  16. import { Directive } from './directives'
  17. import { applyOptions, ComponentOptions } from './componentOptions'
  18. import {
  19. EMPTY_OBJ,
  20. isFunction,
  21. capitalize,
  22. NOOP,
  23. isArray,
  24. isObject
  25. } from '@vue/shared'
  26. import { SuspenseBoundary } from './suspense'
  27. export type Data = { [key: string]: unknown }
  28. export interface FunctionalComponent<P = {}> {
  29. (props: P, ctx: SetupContext): VNodeChild
  30. props?: ComponentPropsOptions<P>
  31. displayName?: string
  32. }
  33. export type Component = ComponentOptions | FunctionalComponent
  34. type LifecycleHook = Function[] | null
  35. export const enum LifecycleHooks {
  36. BEFORE_CREATE = 'bc',
  37. CREATED = 'c',
  38. BEFORE_MOUNT = 'bm',
  39. MOUNTED = 'm',
  40. BEFORE_UPDATE = 'bu',
  41. UPDATED = 'u',
  42. BEFORE_UNMOUNT = 'bum',
  43. UNMOUNTED = 'um',
  44. DEACTIVATED = 'da',
  45. ACTIVATED = 'a',
  46. RENDER_TRIGGERED = 'rtg',
  47. RENDER_TRACKED = 'rtc',
  48. ERROR_CAPTURED = 'ec'
  49. }
  50. type Emit = ((event: string, ...args: unknown[]) => void)
  51. export interface SetupContext {
  52. attrs: Data
  53. slots: Slots
  54. emit: Emit
  55. }
  56. type RenderFunction = () => VNodeChild
  57. export interface ComponentInternalInstance {
  58. type: FunctionalComponent | ComponentOptions
  59. parent: ComponentInternalInstance | null
  60. appContext: AppContext
  61. root: ComponentInternalInstance
  62. vnode: VNode
  63. next: VNode | null
  64. subTree: VNode
  65. update: ReactiveEffect
  66. render: RenderFunction | null
  67. effects: ReactiveEffect[] | null
  68. provides: Data
  69. components: Record<string, Component>
  70. directives: Record<string, Directive>
  71. asyncDep: Promise<any> | null
  72. asyncResult: any
  73. asyncResolved: boolean
  74. // the rest are only for stateful components
  75. renderContext: Data
  76. data: Data
  77. props: Data
  78. attrs: Data
  79. slots: Slots
  80. renderProxy: ComponentPublicInstance | null
  81. propsProxy: Data | null
  82. setupContext: SetupContext | null
  83. refs: Data
  84. emit: Emit
  85. // user namespace
  86. user: { [key: string]: any }
  87. // lifecycle
  88. isUnmounted: boolean
  89. [LifecycleHooks.BEFORE_CREATE]: LifecycleHook
  90. [LifecycleHooks.CREATED]: LifecycleHook
  91. [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook
  92. [LifecycleHooks.MOUNTED]: LifecycleHook
  93. [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook
  94. [LifecycleHooks.UPDATED]: LifecycleHook
  95. [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook
  96. [LifecycleHooks.UNMOUNTED]: LifecycleHook
  97. [LifecycleHooks.RENDER_TRACKED]: LifecycleHook
  98. [LifecycleHooks.RENDER_TRIGGERED]: LifecycleHook
  99. [LifecycleHooks.ACTIVATED]: LifecycleHook
  100. [LifecycleHooks.DEACTIVATED]: LifecycleHook
  101. [LifecycleHooks.ERROR_CAPTURED]: LifecycleHook
  102. }
  103. const emptyAppContext = createAppContext()
  104. export function createComponentInstance(
  105. vnode: VNode,
  106. parent: ComponentInternalInstance | null
  107. ): ComponentInternalInstance {
  108. // inherit parent app context - or - if root, adopt from root vnode
  109. const appContext =
  110. (parent ? parent.appContext : vnode.appContext) || emptyAppContext
  111. const instance = {
  112. vnode,
  113. parent,
  114. appContext,
  115. type: vnode.type as Component,
  116. root: null as any, // set later so it can point to itself
  117. next: null,
  118. subTree: null as any,
  119. update: null as any,
  120. render: null,
  121. renderProxy: null,
  122. propsProxy: null,
  123. setupContext: null,
  124. effects: null,
  125. provides: parent ? parent.provides : Object.create(appContext.provides),
  126. // setup context properties
  127. renderContext: EMPTY_OBJ,
  128. data: EMPTY_OBJ,
  129. props: EMPTY_OBJ,
  130. attrs: EMPTY_OBJ,
  131. slots: EMPTY_OBJ,
  132. refs: EMPTY_OBJ,
  133. // per-instance asset storage (mutable during options resolution)
  134. components: Object.create(appContext.components),
  135. directives: Object.create(appContext.directives),
  136. // async dependency management
  137. asyncDep: null,
  138. asyncResult: null,
  139. asyncResolved: false,
  140. // user namespace for storing whatever the user assigns to `this`
  141. user: {},
  142. // lifecycle hooks
  143. // not using enums here because it results in computed properties
  144. isUnmounted: false,
  145. bc: null,
  146. c: null,
  147. bm: null,
  148. m: null,
  149. bu: null,
  150. u: null,
  151. um: null,
  152. bum: null,
  153. da: null,
  154. a: null,
  155. rtg: null,
  156. rtc: null,
  157. ec: null,
  158. emit: (event: string, ...args: unknown[]) => {
  159. const props = instance.vnode.props || EMPTY_OBJ
  160. const handler = props[`on${event}`] || props[`on${capitalize(event)}`]
  161. if (handler) {
  162. if (isArray(handler)) {
  163. for (let i = 0; i < handler.length; i++) {
  164. callWithAsyncErrorHandling(
  165. handler[i],
  166. instance,
  167. ErrorCodes.COMPONENT_EVENT_HANDLER,
  168. args
  169. )
  170. }
  171. } else {
  172. callWithAsyncErrorHandling(
  173. handler,
  174. instance,
  175. ErrorCodes.COMPONENT_EVENT_HANDLER,
  176. args
  177. )
  178. }
  179. }
  180. }
  181. }
  182. instance.root = parent ? parent.root : instance
  183. return instance
  184. }
  185. export let currentInstance: ComponentInternalInstance | null = null
  186. export let currentSuspense: SuspenseBoundary | null = null
  187. export const getCurrentInstance: () => ComponentInternalInstance | null = () =>
  188. currentInstance
  189. export const setCurrentInstance = (
  190. instance: ComponentInternalInstance | null
  191. ) => {
  192. currentInstance = instance
  193. }
  194. export function setupStatefulComponent(
  195. instance: ComponentInternalInstance,
  196. parentSuspense: SuspenseBoundary | null
  197. ) {
  198. const Component = instance.type as ComponentOptions
  199. // 1. create render proxy
  200. instance.renderProxy = new Proxy(instance, PublicInstanceProxyHandlers) as any
  201. // 2. create props proxy
  202. // the propsProxy is a reactive AND readonly proxy to the actual props.
  203. // it will be updated in resolveProps() on updates before render
  204. const propsProxy = (instance.propsProxy = readonly(instance.props))
  205. // 3. call setup()
  206. const { setup } = Component
  207. if (setup) {
  208. const setupContext = (instance.setupContext =
  209. setup.length > 1 ? createSetupContext(instance) : null)
  210. currentInstance = instance
  211. currentSuspense = parentSuspense
  212. const setupResult = callWithErrorHandling(
  213. setup,
  214. instance,
  215. ErrorCodes.SETUP_FUNCTION,
  216. [propsProxy, setupContext]
  217. )
  218. currentInstance = null
  219. currentSuspense = null
  220. if (
  221. setupResult &&
  222. isFunction(setupResult.then) &&
  223. isFunction(setupResult.catch)
  224. ) {
  225. if (__FEATURE_SUSPENSE__) {
  226. // async setup returned Promise.
  227. // bail here and wait for re-entry.
  228. instance.asyncDep = setupResult as Promise<any>
  229. } else if (__DEV__) {
  230. warn(
  231. `setup() returned a Promise, but the version of Vue you are using ` +
  232. `does not support it yet.`
  233. )
  234. }
  235. return
  236. } else {
  237. handleSetupResult(instance, setupResult, parentSuspense)
  238. }
  239. } else {
  240. finishComponentSetup(instance, parentSuspense)
  241. }
  242. }
  243. export function handleSetupResult(
  244. instance: ComponentInternalInstance,
  245. setupResult: unknown,
  246. parentSuspense: SuspenseBoundary | null
  247. ) {
  248. if (isFunction(setupResult)) {
  249. // setup returned an inline render function
  250. instance.render = setupResult as RenderFunction
  251. } else if (isObject(setupResult)) {
  252. // setup returned bindings.
  253. // assuming a render function compiled from template is present.
  254. instance.renderContext = reactive(setupResult)
  255. } else if (__DEV__ && setupResult !== undefined) {
  256. warn(
  257. `setup() should return an object. Received: ${
  258. setupResult === null ? 'null' : typeof setupResult
  259. }`
  260. )
  261. }
  262. finishComponentSetup(instance, parentSuspense)
  263. }
  264. function finishComponentSetup(
  265. instance: ComponentInternalInstance,
  266. parentSuspense: SuspenseBoundary | null
  267. ) {
  268. const Component = instance.type as ComponentOptions
  269. if (!instance.render) {
  270. if (__DEV__ && !Component.render) {
  271. warn(
  272. `Component is missing render function. Either provide a template or ` +
  273. `return a render function from setup().`
  274. )
  275. }
  276. instance.render = (Component.render || NOOP) as RenderFunction
  277. }
  278. // support for 2.x options
  279. if (__FEATURE_OPTIONS__) {
  280. currentInstance = instance
  281. currentSuspense = parentSuspense
  282. applyOptions(instance, Component)
  283. currentInstance = null
  284. currentSuspense = null
  285. }
  286. if (instance.renderContext === EMPTY_OBJ) {
  287. instance.renderContext = reactive({})
  288. }
  289. }
  290. // used to identify a setup context proxy
  291. export const SetupProxySymbol = Symbol()
  292. const SetupProxyHandlers: { [key: string]: ProxyHandler<any> } = {}
  293. ;['attrs', 'slots', 'refs'].forEach((type: string) => {
  294. SetupProxyHandlers[type] = {
  295. get: (instance, key) => (instance[type] as any)[key],
  296. has: (instance, key) =>
  297. key === SetupProxySymbol || key in (instance[type] as any),
  298. ownKeys: instance => Reflect.ownKeys(instance[type] as any),
  299. // this is necessary for ownKeys to work properly
  300. getOwnPropertyDescriptor: (instance, key) =>
  301. Reflect.getOwnPropertyDescriptor(instance[type], key),
  302. set: () => false,
  303. deleteProperty: () => false
  304. }
  305. })
  306. function createSetupContext(instance: ComponentInternalInstance): SetupContext {
  307. const context = {
  308. // attrs, slots & refs are non-reactive, but they need to always expose
  309. // the latest values (instance.xxx may get replaced during updates) so we
  310. // need to expose them through a proxy
  311. attrs: new Proxy(instance, SetupProxyHandlers.attrs),
  312. slots: new Proxy(instance, SetupProxyHandlers.slots),
  313. refs: new Proxy(instance, SetupProxyHandlers.refs),
  314. emit: instance.emit
  315. } as any
  316. return __DEV__ ? Object.freeze(context) : context
  317. }