🌍 background

In the front-end project business, communication between components is very frequent. Father to son, son to father, brother to brother and so on. Notification of events between multiple components can sometimes be a pain, and EventEmitter makes this process even easier. Using EventEmitter can communicate with multiple components. The specific principle is to share a global instance of a class through props or Context. It has no event name. You need to manually manage events yourself at the pass…

📖 expectations

  • Multi-component communication;
  • You can emit event names and receive them via ON, similar to vue eventBus.
  • It can be shared globally or locally.


📡 The new useEventEmitter

1.1. Main functions

The main functions are divided into two categories, local sharing and global sharing

  • Global specifies whether the share is global.
  • The feature of global sharing is that all components that use hook modification have the ability of global sharing, and there is no need to pass event instances at the top level. Belong to the same instance;
  • The feature of local sharing is that all local sharing needs to pass the Event instance, and multiple local sharing instances can be created. Belong to the same class;
  • Global and local events are independent of each other;

1.2. Principle realization

  • Declare a class that defines a private map
  • Emit corresponds to a map set operation, and on corresponds to a GET operation that retrieves the parameters passed in and calls back through the listener

1.3, paste source 👇

event.js

import { cloneDeep } from "lodash"; type Subscription<T> = ({ params, event, }: { params: T; event: string; }) => void; const subscriptionValueIsArray = (values? : unknown): values is any[] => { return Array.isArray(values); }; class EventEmitter<T> { private subscriptions = new Map<string | number, T>(); constructor() { this.clear(); } on = (event: string, listener? : Subscription<T>) => { 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 };Copy the code

index.ts

import { useEffect, useMemo, useRef } from "react"; import { EventEmitter, eventEmitterOverall } from "./event"; export default function useEventEmitter<T = void>(options? : { global? : boolean; }) { const ref = useRef<EventEmitter<T> | 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; }Copy the code

🔨 use

const eventBus = useEventEmitter({ global: true }); eventBus? .emit("hello", { name: "react" }, { name: "typescript" }); eventBus? .on("hello", (value) => { console.log("hello", value); });Copy the code