Mitt源码学习
介绍
mitt: Tiny 200b functional event emitter / pubsub.
- Microscopic: weighs less than 200 bytes gzipped
- Useful: a wildcard
"*"
event type listens to all events - Familiar: same names & ideas as Node's EventEmitter
- Functional: methods don't rely on this
- Great Name: somehow mitt wasn't taken
Mitt was made for the browser, but works in any JavaScript runtime. It has no dependencies and supports IE9+.
简单地说,mitt
是一个轻量级的JavaScript事件总线库,拥有简单而强大的API,总体大小仅为200字节。它具有与Node.js的EventEmitter类似的API和命名约定,并且没有依赖项。它支持IE9+以及任何JavaScript运行时环境。
使用示例
Details
import mitt from 'mitt'
const emitter = mitt()
// listen to an event
emitter.on('foo', e => console.log('foo', e) )
// listen to all events
emitter.on('*', (type, e) => console.log(type, e) )
// fire an event
emitter.emit('foo', { a: 'b' })
// clearing all events
emitter.all.clear()
// working with handler references:
function onFoo() {}
emitter.on('foo', onFoo) // listen
emitter.off('foo', onFoo) // unlisten
源码
源码非常精简,不过,为了方便阅读,下面提供了js源码及类型声明:
export type EventType = string | symbol;
// An event handler can take an optional event argument
// and should not return a value
export type Handler<T = unknown> = (event: T) => void;
export type WildcardHandler<T = Record<string, unknown>> = (
type: keyof T,
event: T[keyof T]
) => void;
// An array of all currently registered event handlers for a type
export type EventHandlerList<T = unknown> = Array<Handler<T>>;
export type WildCardEventHandlerList<T = Record<string, unknown>> = Array<
WildcardHandler<T>
>;
// A map of event types and their corresponding event handlers.
export type EventHandlerMap<Events extends Record<EventType, unknown>> = Map<
keyof Events | '*',
EventHandlerList<Events[keyof Events]> | WildCardEventHandlerList<Events>
>;
export interface Emitter<Events extends Record<EventType, unknown>> {
all: EventHandlerMap<Events>;
on<Key extends keyof Events>(type: Key, handler: Handler<Events[Key]>): void;
on(type: '*', handler: WildcardHandler<Events>): void;
off<Key extends keyof Events>(
type: Key,
handler?: Handler<Events[Key]>
): void;
off(type: '*', handler: WildcardHandler<Events>): void;
emit<Key extends keyof Events>(type: Key, event: Events[Key]): void;
emit<Key extends keyof Events>(
type: undefined extends Events[Key] ? Key : never
): void;
}
/**
* Mitt: Tiny (~200b) functional event emitter / pubsub.
* @name mitt
* @returns {Mitt}
*/
export default function mitt<Events extends Record<EventType, unknown>>(
all?: EventHandlerMap<Events>
): Emitter<Events> {
type GenericEventHandler =
| Handler<Events[keyof Events]>
| WildcardHandler<Events>;
all = all || new Map();
return {
/**
* A Map of event names to registered handler functions.
*/
all,
/**
* Register an event handler for the given type.
* @param {string|symbol} type Type of event to listen for, or `'*'` for all events
* @param {Function} handler Function to call in response to given event
* @memberOf mitt
*/
on<Key extends keyof Events>(type: Key, handler: GenericEventHandler) {
const handlers: Array<GenericEventHandler> | undefined = all!.get(type);
if (handlers) {
handlers.push(handler);
} else {
all!.set(type, [handler] as EventHandlerList<Events[keyof Events]>);
}
},
/**
* Remove an event handler for the given type.
* If `handler` is omitted, all handlers of the given type are removed.
* @param {string|symbol} type Type of event to unregister `handler` from (`'*'` to remove a wildcard handler)
* @param {Function} [handler] Handler function to remove
* @memberOf mitt
*/
off<Key extends keyof Events>(type: Key, handler?: GenericEventHandler) {
const handlers: Array<GenericEventHandler> | undefined = all!.get(type);
if (handlers) {
if (handler) {
handlers.splice(handlers.indexOf(handler) >>> 0, 1);
} else {
all!.set(type, []);
}
}
},
/**
* Invoke all handlers for the given type.
* If present, `'*'` handlers are invoked after type-matched handlers.
*
* Note: Manually firing '*' handlers is not supported.
*
* @param {string|symbol} type The event type to invoke
* @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler
* @memberOf mitt
*/
emit<Key extends keyof Events>(type: Key, evt?: Events[Key]) {
let handlers = all!.get(type);
if (handlers) {
(handlers as EventHandlerList<Events[keyof Events]>)
.slice()
.map((handler) => {
handler(evt!);
});
}
handlers = all!.get('*');
if (handlers) {
(handlers as WildCardEventHandlerList<Events>)
.slice()
.map((handler) => {
handler(type, evt!);
});
}
}
};
}
export default function mitt(all) {
all = all || new Map()
return {
all,
on(type, handler) {
const handlers = all.get(type)
if (handlers) {
handlers.push(handler)
} else {
all.set(type, [handler])
}
},
off(type, handler) {
const handlers = all.get(type)
if (handlers) {
if (handler) {
handlers.splice(handlers.indexOf(handler) >>> 0, 1)
} else {
all.set(type, [])
}
}
},
emit(type, evt) {
let handlers = all.get(type)
if (handlers) {
handlers.slice().map((handler) => {
handler(evt)
})
}
handlers = all.get('*')
if (handlers) {
handlers.slice().map((handler) => {
handler(type, evt)
})
}
}
}
}
export type EventType = string | symbol
export type Handler<T = unknown> = (event: T) => void
export type WildcardHandler<T = Record<string, unknown>> = (
type: keyof T,
event: T[keyof T]
) => void
export type EventHandlerList<T = unknown> = Array<Handler<T>>
export type WildCardEventHandlerList<T = Record<string, unknown>> = Array<WildcardHandler<T>>
export type EventHandlerMap<Events extends Record<EventType, unknown>> = Map<
keyof Events | '*',
EventHandlerList<Events[keyof Events]> | WildCardEventHandlerList<Events>
>
export interface Emitter<Events extends Record<EventType, unknown>> {
all: EventHandlerMap<Events>
on<Key extends keyof Events>(type: Key, handler: Handler<Events[Key]>): void
on(type: '*', handler: WildcardHandler<Events>): void
off<Key extends keyof Events>(type: Key, handler?: Handler<Events[Key]>): void
off(type: '*', handler: WildcardHandler<Events>): void
emit<Key extends keyof Events>(type: Key, event: Events[Key]): void
emit<Key extends keyof Events>(type: undefined extends Events[Key] ? Key : never): void
}
/**
* Mitt: Tiny (~200b) functional event emitter / pubsub.
* @name mitt
* @returns {Mitt}
*/
export default function mitt<Events extends Record<EventType, unknown>>(
all?: EventHandlerMap<Events>
): Emitter<Events>
分析
- all
源码中可以清晰地看到,mitt 方法利用闭包,使用一个 Map
对象存储事件及对应处理方法列表(type => handlers
,handlers: [handler1,handler2,...]
)
- on & off
on
, off
就是一个按事件名添加/移除 handler
。
需要注意的是调用 off
而不指定 handler
时会移除该事件所有的 handler
。
另外,*
事件 handler
参数有所不同,它多了一个参数 type
。
- emit
触发事件,除了执行该事件的 handlers
外,还会执行 *
事件的 handlers
(如果存在)。
需要注意的是,emit('*', evt)
是毫无意义的,参考源码,*
的 handlers
会执行两次,且两次都是错误的传参。
- all.clear()
使用 all.clear()
清空所有事件的事件监听,通过源码可以知道,由于 all
是直接暴露出来的 Map
对象,all.clear()
实际上就是 Map.prototype.clear()
方法。(完全可以使用 all.has()
, all.get()
, all.keys()
方法读取事件相关信息。通过 all.delete(type)
移除事件虽然可行,却是不规范的。)
小结
实际上事件监听(handler
)的添加就是定义回调函数,以便在未来触发(emit
)时找到并执行它们。
通过 on 添加监听,off 移除监听(一个或全部),emit 触发事件,all.clear 清空所有事件监听。
演练
在vue中跨组件使用时,需要注意它的作用域
下面提供一个简单的hook, useEventBus.js
:
import mitt from 'mitt'
const emitter = mitt()
export const useEventBus = () => {
return {
on: emitter.on,
off: emitter.off,
emit: emitter.emit,
all: emitter.all
}
}
使用:
import { useEventBus } from 'path/to/useEventBus.js'
const { on, off, all, emit } = useEventBus()
mitt VS vue$emit
vue父子组件通信中,emit是非常重要的方法之一。父组件内调用子组件时绑定方法/事件,子组件内使用 $emit 调用/触发。
对比之下,和 mitt 的 on/emit 是否很相似呢?最大的不同之处在于vue只能在父子组件上使用
mitt无需同组件绑定,使用起来要方便得多。但代价就是项目中大量使用时,你需要花更多的时间理清依赖关系
- via mitt
import mitt from 'mitt'
import { onBeforeUnmount } from 'vue'
const emitter = mitt()
export const useEventBus = (option) => {
if (Array.isArray(option)) {
option.forEach((item) => {
emitter.on(item.name, item.callback)
})
onBeforeUnmount(() => {
option.forEach((item) => {
emitter.off(item.name)
})
})
} else if (option) {
emitter.on(option.name, option.callback)
onBeforeUnmount(() => {
emitter.off(option.name)
})
}
return {
on: emitter.on,
off: emitter.off,
emit: emitter.emit,
all: emitter.all
}
}
<script setup>
import { useEventBus } from './useEventBus'
import Grandson from './Grandson.vue'
useEventBus({
name: 'nameChange',
callback: handleNameChange
})
function handleNameChange(val) {
console.log('Name changed:', val)
}
</script>
<template>
<div>
<!-- import Grandson somewhere -->
<Grandson />
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useEventBus } from './useEventBus'
const { emit } = useEventBus()
const name = ref('abc')
</script>
<template>
<input v-model="name" @input="emit('nameChange', name)">
</template>
- without mitt
<script setup>
import { ref, provide, watch } from 'vue'
import Grandson from './Grandson.vue'
const name = ref()
provide('changeName', val => name.value = val)
watch(name, handleNameChange)
function handleNameChange(val) {
console.log('Name changed:', val)
}
</script>
<template>
<div>
<!-- import Grandson somewhere -->
<Grandson />
</div>
</template>
<script setup>
import { ref, inject } from 'vue'
const changeName = inject('changeName', val => {})
const name = ref('abc')
</script>
<template>
<input v-model="name" @input="changeName(name)">
</template>
Last updated: