diff --git a/packages/core/src/component.ts b/packages/core/src/component.ts index dbe4d8e4..604ceeac 100644 --- a/packages/core/src/component.ts +++ b/packages/core/src/component.ts @@ -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
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
{ @@ -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 } } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 14b544de..a4aeadea 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -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' diff --git a/packages/core/src/optional/directive.ts b/packages/core/src/optional/directives.ts similarity index 100% rename from packages/core/src/optional/directive.ts rename to packages/core/src/optional/directives.ts diff --git a/packages/core/src/optional/events.ts b/packages/core/src/optional/events.ts new file mode 100644 index 00000000..2fba5fc3 --- /dev/null +++ b/packages/core/src/optional/events.ts @@ -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) + } +} diff --git a/packages/core/src/optional/mixin.ts b/packages/core/src/optional/mixins.ts similarity index 100% rename from packages/core/src/optional/mixin.ts rename to packages/core/src/optional/mixins.ts