component.ts 11 KB

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