component.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. import { EMPTY_OBJ, NOOP } from '@vue/shared'
  2. import { VNode, Slots, RenderNode, MountedVNode } from './vdom'
  3. import {
  4. Data,
  5. ComponentOptions,
  6. ComponentClassOptions,
  7. ComponentPropsOptions,
  8. WatchOptions
  9. } from './componentOptions'
  10. import { setupWatcher } from './componentWatch'
  11. import { Autorun, DebuggerEvent, ComputedGetter } from '@vue/observer'
  12. import { nextTick } from '@vue/scheduler'
  13. import { ErrorTypes } from './errorHandling'
  14. import { initializeComponentInstance } from './componentUtils'
  15. import { EventEmitter, invokeListeners } from './optional/eventEmitter'
  16. import { warn } from './warning'
  17. // public component instance type
  18. export interface Component<P = {}, D = {}> extends PublicInstanceMethods {
  19. readonly $el: any
  20. readonly $vnode: MountedVNode
  21. readonly $parentVNode: MountedVNode
  22. readonly $data: D
  23. readonly $props: Readonly<P>
  24. readonly $attrs: Readonly<Data>
  25. readonly $slots: Slots
  26. readonly $root: Component
  27. readonly $parent: Component
  28. readonly $children: Component[]
  29. readonly $options: ComponentOptions<P, D, this>
  30. readonly $refs: Record<string | symbol, any>
  31. readonly $proxy: this
  32. }
  33. interface PublicInstanceMethods {
  34. $forceUpdate(): void
  35. $nextTick(fn: () => any): Promise<void>
  36. $watch(
  37. keyOrFn: string | ((this: this) => any),
  38. cb: (this: this, newValue: any, oldValue: any) => void,
  39. options?: WatchOptions
  40. ): () => void
  41. $emit(name: string, ...payload: any[]): void
  42. }
  43. export interface APIMethods<P = {}, D = {}> {
  44. data(): Partial<D>
  45. hooks(): any
  46. render(props: Readonly<P>, slots: Slots, attrs: Data, parentVNode: VNode): any
  47. }
  48. export interface LifecycleMethods {
  49. beforeCreate(): void
  50. created(): void
  51. beforeMount(): void
  52. mounted(): void
  53. beforeUpdate(vnode: VNode): void
  54. updated(vnode: VNode): void
  55. beforeUnmount(): void
  56. unmounted(): void
  57. errorCaptured(): (
  58. err: Error,
  59. type: ErrorTypes,
  60. instance: ComponentInstance | null,
  61. vnode: VNode
  62. ) => boolean | void
  63. activated(): void
  64. deactivated(): void
  65. renderTracked(e: DebuggerEvent): void
  66. renderTriggered(e: DebuggerEvent): void
  67. }
  68. export interface ComponentClass extends ComponentClassOptions {
  69. options?: ComponentOptions
  70. new <P = {}, D = {}>(): Component<P, D> & D & P
  71. }
  72. export interface FunctionalComponent<P = {}> {
  73. (props: P, slots: Slots, attrs: Data, parentVNode: VNode): any
  74. pure?: boolean
  75. props?: ComponentPropsOptions<P>
  76. displayName?: string
  77. }
  78. export type ComponentType = ComponentClass | FunctionalComponent
  79. // Internal type that represents a mounted instance.
  80. // It extends InternalComponent with mounted instance properties.
  81. export interface ComponentInstance<P = {}, D = {}>
  82. extends InternalComponent,
  83. Partial<APIMethods<P, D>>,
  84. Partial<LifecycleMethods> {
  85. constructor: ComponentClass
  86. render: APIMethods<P, D>['render']
  87. $vnode: MountedVNode
  88. $data: D
  89. $props: P
  90. $attrs: Data
  91. $slots: Slots
  92. $root: ComponentInstance
  93. $children: ComponentInstance[]
  94. $options: ComponentOptions<P, D>
  95. _updateHandle: Autorun
  96. _queueJob: ((fn: () => void) => void)
  97. _self: ComponentInstance<P, D> // on proxies only
  98. }
  99. // actual implementation of the component
  100. class InternalComponent implements PublicInstanceMethods {
  101. get $el(): any {
  102. return this.$vnode && this.$vnode.el
  103. }
  104. $vnode: VNode | null = null
  105. $parentVNode: VNode | null = null
  106. $data: Data | null = null
  107. $props: Data | null = null
  108. $attrs: Data | null = null
  109. $slots: Slots | null = null
  110. $root: ComponentInstance | null = null
  111. $parent: ComponentInstance | null = null
  112. $children: ComponentInstance[] = []
  113. $options: ComponentOptions | null = null
  114. $refs: Record<string, ComponentInstance | RenderNode> = {}
  115. $proxy: any = null
  116. _rawData: Data | null = null
  117. _computedGetters: Record<string, ComputedGetter> | null = null
  118. _watchHandles: Set<Autorun> | null = null
  119. _mounted: boolean = false
  120. _unmounted: boolean = false
  121. _events: { [event: string]: Function[] | null } | null = null
  122. _updateHandle: Autorun | null = null
  123. _queueJob: ((fn: () => void) => void) | null = null
  124. _isVue: boolean = true
  125. _inactiveRoot: boolean = false
  126. _hookProps: any = null
  127. constructor(props?: object) {
  128. if (props === void 0) {
  129. initializeComponentInstance(this as any)
  130. } else {
  131. // the presence of the props argument indicates that this class is being
  132. // instantiated as a mixin, and should expose the props on itself
  133. // so that the extended class constructor (and property initializers) can
  134. // access $props.
  135. this.$props = props
  136. }
  137. if (__COMPAT__) {
  138. ;(this as any)._eventEmitter = new EventEmitter(this)
  139. }
  140. }
  141. // necessary to tell this apart from a functional
  142. render(...args: any[]): any {
  143. if (__DEV__) {
  144. const name =
  145. (this.$options && this.$options.displayName) || this.constructor.name
  146. warn(`Class component \`${name}\` is missing render() method.`)
  147. }
  148. }
  149. // to be set by renderer during mount
  150. $forceUpdate: () => void = NOOP
  151. $nextTick(fn: () => any): Promise<void> {
  152. return nextTick(fn)
  153. }
  154. $watch(
  155. keyOrFn: string | ((this: this) => any),
  156. cb: (this: this, newValue: any, oldValue: any) => void,
  157. options?: WatchOptions
  158. ): () => void {
  159. return setupWatcher(this as any, keyOrFn, cb, options)
  160. }
  161. $emit(name: string, ...payload: any[]) {
  162. const parentData =
  163. (this.$parentVNode && this.$parentVNode.data) || EMPTY_OBJ
  164. const parentListener =
  165. parentData['on' + name] || parentData['on' + name.toLowerCase()]
  166. if (parentListener) {
  167. invokeListeners(parentListener, payload)
  168. }
  169. }
  170. }
  171. // legacy event emitter interface exposed on component instances
  172. if (__COMPAT__) {
  173. const p = InternalComponent.prototype as any
  174. ;['on', 'off', 'once'].forEach(key => {
  175. p['$' + key] = function(...args: any[]) {
  176. this._eventEmitter[key](...args)
  177. return this
  178. }
  179. })
  180. const emit = p.$emit
  181. p.$emit = function(...args: any[]) {
  182. emit.call(this, ...args)
  183. this._eventEmitter.emit(...args)
  184. return this
  185. }
  186. }
  187. // the exported Component has the implementation details of the actual
  188. // InternalComponent class but with proper type inference of ComponentClass.
  189. export const Component = InternalComponent as ComponentClass