refactor: split EventEmitter and make it optional
This commit is contained in:
parent
7ce16ea8d6
commit
861a1c23b0
@ -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 { VNode, Slots, RenderNode, MountedVNode } from './vdom'
|
||||||
import {
|
import {
|
||||||
Data,
|
Data,
|
||||||
@ -12,6 +12,7 @@ import { Autorun, DebuggerEvent, ComputedGetter } from '@vue/observer'
|
|||||||
import { nextTick } from '@vue/scheduler'
|
import { nextTick } from '@vue/scheduler'
|
||||||
import { ErrorTypes } from './errorHandling'
|
import { ErrorTypes } from './errorHandling'
|
||||||
import { initializeComponentInstance } from './componentUtils'
|
import { initializeComponentInstance } from './componentUtils'
|
||||||
|
import { EventEmitter, invokeListeners } from './optional/events'
|
||||||
|
|
||||||
// public component instance type
|
// public component instance type
|
||||||
export interface Component<P = {}, D = {}> extends PublicInstanceMethods {
|
export interface Component<P = {}, D = {}> extends PublicInstanceMethods {
|
||||||
@ -38,10 +39,7 @@ interface PublicInstanceMethods {
|
|||||||
cb: (this: this, newValue: any, oldValue: any) => void,
|
cb: (this: this, newValue: any, oldValue: any) => void,
|
||||||
options?: WatchOptions
|
options?: WatchOptions
|
||||||
): () => void
|
): () => void
|
||||||
$on(event: string, fn: Function): this
|
$emit(name: string, ...payload: any[]): void
|
||||||
$once(event: string, fn: Function): this
|
|
||||||
$off(event?: string, fn?: Function): this
|
|
||||||
$emit(name: string, ...payload: any[]): this
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface APIMethods<P = {}, D = {}> {
|
export interface APIMethods<P = {}, D = {}> {
|
||||||
@ -147,6 +145,9 @@ class InternalComponent implements PublicInstanceMethods {
|
|||||||
// access $props.
|
// access $props.
|
||||||
this.$props = props
|
this.$props = props
|
||||||
}
|
}
|
||||||
|
if (__COMPAT__) {
|
||||||
|
;(this as any)._eventEmitter = new EventEmitter(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// to be set by renderer during mount
|
// to be set by renderer during mount
|
||||||
@ -164,55 +165,7 @@ class InternalComponent implements PublicInstanceMethods {
|
|||||||
return setupWatcher(this as any, keyOrFn, cb, options)
|
return setupWatcher(this as any, keyOrFn, cb, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
// eventEmitter interface
|
$emit(name: string, ...payload: any[]) {
|
||||||
$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 {
|
|
||||||
const parentData =
|
const parentData =
|
||||||
(this.$parentVNode && this.$parentVNode.data) || EMPTY_OBJ
|
(this.$parentVNode && this.$parentVNode.data) || EMPTY_OBJ
|
||||||
const parentListener =
|
const parentListener =
|
||||||
@ -220,24 +173,23 @@ class InternalComponent implements PublicInstanceMethods {
|
|||||||
if (parentListener) {
|
if (parentListener) {
|
||||||
invokeListeners(parentListener, payload)
|
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[]) {
|
// legacy event emitter interface exposed on component instances
|
||||||
// TODO handle error
|
if (__COMPAT__) {
|
||||||
if (isArray(value)) {
|
const p = InternalComponent.prototype as any
|
||||||
for (let i = 0; i < value.length; i++) {
|
;['on', 'off', 'once'].forEach(key => {
|
||||||
value[i](...payload)
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,11 +12,12 @@ export { nextTick } from '@vue/scheduler'
|
|||||||
|
|
||||||
// Optional APIs
|
// Optional APIs
|
||||||
// these are imported on-demand and can be tree-shaken
|
// 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 { Provide, Inject } from './optional/context'
|
||||||
export { createAsyncComponent } from './optional/asyncComponent'
|
export { createAsyncComponent } from './optional/asyncComponent'
|
||||||
export { KeepAlive } from './optional/keepAlive'
|
export { KeepAlive } from './optional/keepAlive'
|
||||||
export { mixins } from './optional/mixin'
|
export { mixins } from './optional/mixins'
|
||||||
|
export { EventEmitter } from './optional/events'
|
||||||
|
|
||||||
// flags & types
|
// flags & types
|
||||||
export { ComponentType, ComponentClass, FunctionalComponent } from './component'
|
export { ComponentType, ComponentClass, FunctionalComponent } from './component'
|
||||||
|
76
packages/core/src/optional/events.ts
Normal file
76
packages/core/src/optional/events.ts
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user