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

201 lines
5.7 KiB
TypeScript
Raw Normal View History

2018-09-19 23:35:38 +08:00
import { EMPTY_OBJ } from './utils'
import { VNode, Slots, RenderNode, MountedVNode } from './vdom'
2018-09-19 23:35:38 +08:00
import {
Data,
ComponentOptions,
2018-10-10 08:22:29 +08:00
ComponentClassOptions,
2018-09-24 11:16:14 +08:00
ComponentPropsOptions,
2018-10-05 04:44:23 +08:00
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-10-10 08:22:29 +08:00
import { resolveComponentOptions } from './componentUtils'
2018-09-19 23:35:38 +08:00
2018-10-10 08:22:29 +08:00
export interface ComponentClass extends ComponentClassOptions {
options?: ComponentOptions
2018-10-09 06:09:13 +08:00
new <P extends object = {}, D extends object = {}>(): MergedComponent<P, D>
2018-09-19 23:35:38 +08:00
}
2018-10-09 23:37:24 +08:00
export type MergedComponent<P, D> = D & P & ComponentInstance<P, D>
2018-10-09 06:09:13 +08:00
export interface FunctionalComponent<P = {}> {
(props: Readonly<P>, slots: Slots, attrs: Data): any
2018-09-19 23:35:38 +08:00
pure?: boolean
props?: ComponentPropsOptions<P>
2018-10-09 06:09:13 +08:00
displayName?: string
2018-09-19 23:35:38 +08:00
}
2018-09-26 09:28:52 +08:00
export type ComponentType = ComponentClass | FunctionalComponent
2018-10-09 23:37:24 +08:00
export interface ComponentInstance<P = {}, D = {}> extends InternalComponent {
2018-10-10 08:22:29 +08:00
constructor: ComponentClass
$vnode: MountedVNode
2018-09-19 23:35:38 +08:00
$data: D
2018-10-09 06:09:13 +08:00
$props: Readonly<P>
2018-09-25 06:51:58 +08:00
$attrs: Data
2018-09-19 23:35:38 +08:00
$slots: Slots
2018-10-09 23:37:24 +08:00
$root: ComponentInstance
$children: ComponentInstance[]
2018-09-19 23:35:38 +08:00
2018-10-09 06:09:13 +08:00
data?(): Partial<D>
render(props: Readonly<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
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
errorCaptured?(): (
err: Error,
type: ErrorTypes,
2018-10-09 23:37:24 +08:00
target: ComponentInstance
) => boolean | void
2018-09-27 06:34:21 +08:00
activated?(): void
deactivated?(): 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
2018-10-09 23:37:24 +08:00
_self: ComponentInstance<D, P> // on proxies only
2018-09-19 23:35:38 +08:00
}
2018-09-26 05:49:47 +08:00
class InternalComponent {
public get $el(): RenderNode | null {
2018-09-19 23:35:38 +08:00
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 $slots: Slots | null = null
2018-10-09 23:37:24 +08:00
public $root: ComponentInstance | null = null
public $parent: ComponentInstance | null = null
public $children: ComponentInstance[] = []
2018-10-10 08:22:29 +08:00
public $options: ComponentOptions
2018-10-09 23:37:24 +08:00
public $refs: Record<string, ComponentInstance | 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
2018-09-27 05:10:34 +08:00
public _unmounted: boolean = false
2018-09-19 23:35:38 +08:00
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
2018-09-27 06:34:21 +08:00
public _inactiveRoot: boolean = false
2018-09-19 23:35:38 +08:00
2018-10-10 08:22:29 +08:00
constructor() {
this.$options =
(this.constructor as ComponentClass).options ||
resolveComponentOptions(this.constructor as ComponentClass)
2018-09-19 23:35:38 +08:00
}
$nextTick(fn: () => any): Promise<any> {
return nextTick(fn)
}
2018-09-19 23:35:38 +08:00
$watch(
2018-10-09 23:37:24 +08:00
keyOrFn: string | ((this: this) => any),
cb: (this: this, newValue: any, oldValue: any) => void,
2018-09-24 11:16:14 +08:00
options?: WatchOptions
2018-10-09 23:37:24 +08:00
): () => void {
2018-10-09 06:09:13 +08:00
return setupWatcher(this as any, keyOrFn, cb, options)
2018-09-19 23:35:38 +08:00
}
// eventEmitter interface
2018-10-09 06:09:13 +08:00
$on(event: string, fn: Function): this {
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-10-09 06:09:13 +08:00
$once(event: string, fn: Function): this {
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-10-09 06:09:13 +08:00
$off(event?: string, fn?: Function): this {
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-10-09 06:09:13 +08:00
$emit(name: string, ...payload: any[]): this {
const parentData =
(this.$parentVNode && this.$parentVNode.data) || EMPTY_OBJ
2018-09-19 23:35:38 +08:00
const parentListener =
parentData['on' + name] || parentData['on' + name.toLowerCase()]
2018-09-19 23:35:38 +08:00
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