背景
在前端项目业务中,组件间的通讯是十分频繁的。父传子,子传父,兄弟间等等。在多个组件之间进行事件通知有时会让人非常头疼,借助 EventEmitter ,可以让这一过程变得更加简单。秉承着能白嫖绝不自己动手的原则,我翻看了ahooks,里面 useEventEmitter
可以进行多个组件通讯,具体原理主要是通过 props
或者 Context
共享一个全局的类实例,使用下来体验感不太好,它无事件名称,需要自己在传值处手动管理事件。。。
期望
- 可以多组件通讯;
- 可以emit传递事件名,通过on接收,类似vue的eventBus;
- 可以全局共享,也能局部共享;
全新的useEventEmitter
1.1、主要功能
主要功能分为两大类,局部共享和全局共享
- 通过global配置是否属于全局共享;
- 全局共享特点为,凡是使用改hook的组件内都具备全局共享的能力,不需要在最顶层传递event实例。属于同一个实例;
- 局部共享的特点为,凡是局部共享都需要传递event实例,可创建多个局部共享实例。属于同一个类;
- 全局和局部的事件相互独立;
1.2、 原理实现
- 声明一个类,类中定义一个私有的map
- emit相当于map的set操作,on相当于get操作取得传入的参数,通过listener回调
1.3、贴源码
event.js
import { cloneDeep } from "lodash";
type Subscription = ({
params,
event,
}: {
params: T;
event: string;
}) => void;
const subscriptionValueIsArray = (values?: unknown): values is any[] => {
return Array.isArray(values);
};
class EventEmitter {
private subscriptions = new Map();
constructor() {
this.clear();
}
on = (event: string, listener?: Subscription) => {
if (this.subscriptions.has(event)) {
const subscriptionValues = this.subscriptions.get(event);
if (subscriptionValueIsArray(subscriptionValues))
listener?.({
params: subscriptionValues?.[0] ?? [],
event: subscriptionValues?.[1]?.event,
});
}
};
emit = (event: string | number, ...args: T extends any[] ? any[] : any) => {
if (typeof event === "string" || typeof event === "number")
this.subscriptions.set(
event,
cloneDeep([
args,
{
event,
},
]) as any,
);
else throw new TypeError("event must be string or number !");
};
removeListener = (event: string) => {
this.subscriptions.delete(event);
};
clear = () => {
this.subscriptions.clear();
};
}
const eventEmitterOverall = new EventEmitter();
export { EventEmitter, eventEmitterOverall };
index.ts
import { useEffect, useMemo, useRef } from "react";
import { EventEmitter, eventEmitterOverall } from "./event";
export default function useEventEmitter(options?: {
global?: boolean;
}) {
const ref = useRef | typeof eventEmitterOverall>();
const eventEmitterOptions = useMemo(
() => options ?? { global: false },
[options],
);
ref.current = useMemo(
() =>
eventEmitterOptions.global
? (ref.current = eventEmitterOverall)
: (ref.current = new EventEmitter()),
[eventEmitterOptions],
);
useEffect(() => {
return () => ref.current?.clear();
}, []);
return ref.current;
}
使用
const eventBus = useEventEmitter({ global: true });
eventBus?.emit("hello", { name: "react" }, { name: "typescript" });
eventBus?.on("hello", (value) => {
console.log("hello", value);
});