mitt 是只有 200b 的 EventBus 库,可以在任何 javascript 运行时环境中运行,没有任何依赖,支持 IE9+。源码很精炼,可以在 GitHub 的 src/index.ts 文件中找到源代码。
主体分析
整体就是一个工厂函数,返回一个对象,这个对象包含 all 属性以及on,off,emit 三个方法。all 默认是从 mitt 函数的传参中取值,没有传参则创建一个 map。
export default function mitt<Events extends Record<EventType, unknown>>(
all?: EventHandlerMap<Events>
): Emitter<Events> {
all = all || new Map();
return {
all,
on(){},
off(){},
emit(){},
};
}
on 方法支持传入 type 和 handler 参数,使用 all.get(type) 可以获取到 handlers。如果 handlers 有值则将 handler 放到 handlers 里面,否则将 type 作为键,[handler] 作为值保存到 all 里面。
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]>);
}
}
off 方法可以传入两个参数,一个必填参数 type,另一个是可选参数 handler,从 all 可以获取到键为 type 的 handlers。如果 handlers 有值则继续进行判断,handler 有值则从 handlers 中找到该值并移除,否则 all 将保存键为 type,值为 [] 的情况。这里需要提下 handlers.indexOf(handler) >>> 0,由于 indexOf 存在值为-1而 splice 支持负数的情况,所以这里使用了无符号右移 -1>>>0 的结果是 4294967295,这样 splice 的操作不影响数组
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, []);
}
}
},
emit 方法可以传入一个必填参数 type 和一个可选参数 evt。从 all 中获取到 type 键的 handlers,有值的话进行浅拷贝,将 evt 作为参数遍历调用里面的函数。也支持从 all 中获取到 * 键的 handlers,有值的话进行浅拷贝,将 type 和 evt 作为参数遍历调用里面的函数。
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!);
});
}
}
清除的时候可以利用 map 自身的方法,也就是 all.clear() 即可清除里面的所有键值对
类型分析
mitt 还有完善的类型支持。EventType 是联合类型 string|symbol。WildcardHandler 相比 Handler 增加了一个 type,它需要满足一个键为 string 的范型,也就是 type 必须是范型的键,event 为范型的值。EventHandlerMap 为 all 的类型注解,键值都包含两种情况。Emitter 接口列举了 mitt 所有会出现的属性和方法,其中 on,off,emit 使用了函数重载,每个方法都有两种情况。
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 库源代码较少,但是功能和类型都已具备。除了上面分析的功能和类型,项目代码也有很多值得学习的地方,比如测试代码,里面详细例举了多种情况,代码行数甚至比源代码还要多。
参考资料: 无符号右移