vue3-yuanma/packages/core/src/component.ts

204 lines
5.6 KiB
TypeScript
Raw Normal View History

2018-09-19 23:35:38 +08:00
import { EMPTY_OBJ } from './utils'
import { VNode, Slots, RenderNode, RenderFragment } from './vdom'
import {
Data,
RenderFunction,
ComponentOptions,
2018-09-24 11:16:14 +08:00
ComponentPropsOptions,
WatchOptions
2018-09-19 23:35:38 +08:00
} from './componentOptions'
import { setupWatcher } from './componentWatch'
import { Autorun, DebuggerEvent, ComputedGetter } from '@vue/observer'
import { nextTick } from '@vue/scheduler'
2018-09-24 08:59:19 +08:00
import { ErrorTypes } from './errorHandling'
2018-09-19 23:35:38 +08:00
type Flatten<T> = { [K in keyof T]: T[K] }
2018-09-26 05:49:47 +08:00
export interface ComponentClass extends Flatten<typeof InternalComponent> {
2018-09-19 23:35:38 +08:00
new <D = Data, P = Data>(): MountedComponent<D, P> & D & P
}
export interface FunctionalComponent<P = Data> extends RenderFunction<P> {
pure?: boolean
props?: ComponentPropsOptions<P>
2018-09-24 11:28:21 +08:00
inheritAttrs?: boolean
2018-09-19 23:35:38 +08:00
}
// this interface is merged with the class type
// to represent a mounted component
2018-09-26 05:49:47 +08:00
export interface MountedComponent<D = Data, P = Data>
extends InternalComponent {
2018-09-19 23:35:38 +08:00
$vnode: VNode
$data: D
$props: P
2018-09-25 06:51:58 +08:00
$attrs: Data
2018-09-19 23:35:38 +08:00
$computed: Data
$slots: Slots
$root: MountedComponent
$children: MountedComponent[]
$options: ComponentOptions<D, P>
2018-09-25 07:11:14 +08:00
render(props: P, slots: Slots, attrs: Data): any
2018-09-25 02:05:18 +08:00
renderError?(e: Error): any
renderTracked?(e: DebuggerEvent): void
renderTriggered?(e: DebuggerEvent): void
2018-09-19 23:35:38 +08:00
data?(): Partial<D>
beforeCreate?(): void
created?(): void
beforeMount?(): void
mounted?(): void
2018-09-25 02:05:18 +08:00
beforeUpdate?(vnode: VNode): void
updated?(vnode: VNode): void
2018-09-26 02:56:31 +08:00
beforeUnmount?(): void
unmounted?(): void
2018-09-24 08:59:19 +08:00
errorCaptured?(): (err: Error, type: ErrorTypes) => boolean | void
2018-09-19 23:35:38 +08:00
_updateHandle: Autorun
_queueJob: ((fn: () => void) => void)
2018-09-19 23:35:38 +08:00
$forceUpdate: () => void
$nextTick: (fn: () => void) => Promise<any>
2018-09-19 23:35:38 +08:00
_self: MountedComponent<D, P> // on proxies only
}
2018-09-26 05:49:47 +08:00
class InternalComponent {
2018-09-19 23:35:38 +08:00
public static options?: ComponentOptions
public get $el(): RenderNode | RenderFragment | null {
return this.$vnode && this.$vnode.el
}
public $vnode: VNode | null = null
public $parentVNode: VNode | null = null
public $data: Data | null = null
public $props: Data | null = null
2018-09-25 06:51:58 +08:00
public $attrs: Data | null = null
2018-09-19 23:35:38 +08:00
public $computed: Data | null = null
public $slots: Slots | null = null
public $root: MountedComponent | null = null
public $parent: MountedComponent | null = null
public $children: MountedComponent[] = []
public $options: any
2018-09-24 07:53:19 +08:00
public $refs: Record<string, MountedComponent | RenderNode> = {}
2018-09-19 23:35:38 +08:00
public $proxy: any = null
public $forceUpdate: (() => void) | null = null
public _rawData: Data | null = null
public _computedGetters: Record<string, ComputedGetter> | null = null
public _watchHandles: Set<Autorun> | null = null
public _mounted: boolean = false
public _destroyed: boolean = false
public _events: { [event: string]: Function[] | null } | null = null
public _updateHandle: Autorun | null = null
public _queueJob: ((fn: () => void) => void) | null = null
2018-09-19 23:35:38 +08:00
public _revokeProxy: () => void
public _isVue: boolean = true
constructor(options?: ComponentOptions) {
this.$options = options || (this.constructor as any).options || EMPTY_OBJ
// root instance
if (options !== void 0) {
// mount this
}
}
$nextTick(fn: () => any): Promise<any> {
return nextTick(fn)
}
2018-09-19 23:35:38 +08:00
$watch(
this: MountedComponent,
keyOrFn: string | (() => any),
2018-09-26 05:49:47 +08:00
cb: (newValue: any, oldValue: any) => void,
2018-09-24 11:16:14 +08:00
options?: WatchOptions
2018-09-19 23:35:38 +08:00
) {
2018-09-24 11:16:14 +08:00
return setupWatcher(this, keyOrFn, cb, options)
2018-09-19 23:35:38 +08:00
}
// eventEmitter interface
2018-09-26 05:49:47 +08:00
$on(this: MountedComponent, event: string, fn: Function): MountedComponent {
2018-09-19 23:35:38 +08:00
if (Array.isArray(event)) {
for (let i = 0; i < event.length; i++) {
this.$on(event[i], fn)
}
} else {
const events = this._events || (this._events = Object.create(null))
;(events[event] || (events[event] = [])).push(fn)
}
return this
}
2018-09-26 05:49:47 +08:00
$once(this: MountedComponent, event: string, fn: Function): MountedComponent {
2018-09-19 23:35:38 +08:00
const onceFn = (...args: any[]) => {
this.$off(event, onceFn)
fn.apply(this, args)
}
;(onceFn as any).fn = fn
return this.$on(event, onceFn)
}
2018-09-26 05:49:47 +08:00
$off(
this: MountedComponent,
event?: string,
fn?: Function
): MountedComponent {
2018-09-19 23:35:38 +08:00
if (this._events) {
if (!event && !fn) {
this._events = null
} else if (Array.isArray(event)) {
for (let i = 0; i < event.length; i++) {
this.$off(event[i], fn)
}
} else if (!fn) {
this._events[event as string] = null
} else {
const fns = this._events[event as string]
if (fns) {
for (let i = 0; i < fns.length; i++) {
const f = fns[i]
if (fn === f || fn === (f as any).fn) {
fns.splice(i, 1)
break
}
}
}
}
}
return this
}
2018-09-26 05:49:47 +08:00
$emit(
this: MountedComponent,
name: string,
...payload: any[]
): MountedComponent {
2018-09-19 23:35:38 +08:00
const parentListener =
this.$props['on' + name] || this.$props['on' + name.toLowerCase()]
if (parentListener) {
invokeListeners(parentListener, payload)
}
if (this._events) {
const handlers = this._events[name]
if (handlers) {
invokeListeners(handlers, payload)
}
}
return this
}
}
function invokeListeners(value: Function | Function[], payload: any[]) {
// TODO handle error
if (Array.isArray(value)) {
for (let i = 0; i < value.length; i++) {
value[i](...payload)
}
} else {
value(...payload)
}
}
2018-09-26 05:49:47 +08:00
// the exported Component has the implementation details of the actual
// InternalComponent class but with proper type inference of ComponentClass.
export const Component = InternalComponent as ComponentClass