refactor: split EventEmitter and make it optional

This commit is contained in:
Evan You 2018-10-16 20:32:18 -04:00
parent 7ce16ea8d6
commit 861a1c23b0
5 changed files with 99 additions and 70 deletions

View File

@ -1,4 +1,4 @@
import { EMPTY_OBJ, NOOP, isArray } from '@vue/shared'
import { EMPTY_OBJ, NOOP } from '@vue/shared'
import { VNode, Slots, RenderNode, MountedVNode } from './vdom'
import {
Data,
@ -12,6 +12,7 @@ import { Autorun, DebuggerEvent, ComputedGetter } from '@vue/observer'
import { nextTick } from '@vue/scheduler'
import { ErrorTypes } from './errorHandling'
import { initializeComponentInstance } from './componentUtils'
import { EventEmitter, invokeListeners } from './optional/events'
// public component instance type
export interface Component<P = {}, D = {}> extends PublicInstanceMethods {
@ -38,10 +39,7 @@ interface PublicInstanceMethods {
cb: (this: this, newValue: any, oldValue: any) => void,
options?: WatchOptions
): () => void
$on(event: string, fn: Function): this
$once(event: string, fn: Function): this
$off(event?: string, fn?: Function): this
$emit(name: string, ...payload: any[]): this
$emit(name: string, ...payload: any[]): void
}
export interface APIMethods<P = {}, D = {}> {
@ -147,6 +145,9 @@ class InternalComponent implements PublicInstanceMethods {
// access $props.
this.$props = props
}
if (__COMPAT__) {
;(this as any)._eventEmitter = new EventEmitter(this)
}
}
// to be set by renderer during mount
@ -164,55 +165,7 @@ class InternalComponent implements PublicInstanceMethods {
return setupWatcher(this as any, keyOrFn, cb, options)
}
// eventEmitter interface
$on(event: string, fn: Function): this {
if (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
}
$once(event: string, fn: Function): this {
const onceFn = (...args: any[]) => {
this.$off(event, onceFn)
fn.apply(this, args)
}
;(onceFn as any).fn = fn
return this.$on(event, onceFn)
}
$off(event?: string, fn?: Function): this {
if (this._events) {
if (!event && !fn) {
this._events = null
} else if (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
}
$emit(name: string, ...payload: any[]): this {
$emit(name: string, ...payload: any[]) {
const parentData =
(this.$parentVNode && this.$parentVNode.data) || EMPTY_OBJ
const parentListener =
@ -220,24 +173,23 @@ class InternalComponent implements PublicInstanceMethods {
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 (isArray(value)) {
for (let i = 0; i < value.length; i++) {
value[i](...payload)
// legacy event emitter interface exposed on component instances
if (__COMPAT__) {
const p = InternalComponent.prototype as any
;['on', 'off', 'once'].forEach(key => {
p['$' + key] = function(...args: any[]) {
this._eventEmitter[key](...args)
return this
}
} else {
value(...payload)
})
const emit = p.$emit
p.$emit = function(...args: any[]) {
emit.call(this, ...args)
this._eventEmitter.emit(...args)
return this
}
}

View File

@ -12,11 +12,12 @@ export { nextTick } from '@vue/scheduler'
// Optional APIs
// these are imported on-demand and can be tree-shaken
export { applyDirectives } from './optional/directive'
export { applyDirectives } from './optional/directives'
export { Provide, Inject } from './optional/context'
export { createAsyncComponent } from './optional/asyncComponent'
export { KeepAlive } from './optional/keepAlive'
export { mixins } from './optional/mixin'
export { mixins } from './optional/mixins'
export { EventEmitter } from './optional/events'
// flags & types
export { ComponentType, ComponentClass, FunctionalComponent } from './component'

View File

@ -0,0 +1,76 @@
import { isArray } from '@vue/shared'
export class EventEmitter {
ctx: any
events: { [event: string]: Function[] | null } = {}
constructor(ctx: any) {
this.ctx = ctx
}
// eventEmitter interface
on(event: string, fn: Function) {
if (isArray(event)) {
for (let i = 0; i < event.length; i++) {
this.on(event[i], fn)
}
} else {
const { events } = this
;(events[event] || (events[event] = [])).push(fn)
}
}
once(event: string, fn: Function) {
const onceFn = (...args: any[]) => {
this.off(event, onceFn)
fn.apply(this, args)
}
;(onceFn as any).fn = fn
this.on(event, onceFn)
}
off(event?: string, fn?: Function) {
if (!event && !fn) {
this.events = {}
} else if (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
}
}
}
}
}
emit(name: string, ...payload: any[]) {
const handlers = this.events[name]
if (handlers) {
invokeListeners(handlers, payload, this.ctx)
}
}
}
export function invokeListeners(
value: Function | Function[],
payload: any[],
ctx: any = null
) {
// TODO handle error
if (isArray(value)) {
for (let i = 0; i < value.length; i++) {
value[i].call(ctx, ...payload)
}
} else {
value.call(ctx, ...payload)
}
}