likes
comments
collection
share

React中优雅的使用 useEventEmitter 进行多组件事件共享

作者站长头像
站长
· 阅读数 15

🌍 背景

在前端项目业务中,组件间的通讯是十分频繁的。父传子,子传父,兄弟间等等。在多个组件之间进行事件通知有时会让人非常头疼,借助 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<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 };

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;
}

🔨 使用

  const eventBus = useEventEmitter({ global: true });

  eventBus?.emit("hello", { name: "react" }, { name: "typescript" });
  eventBus?.on("hello", (value) => {
    console.log("hello", value);
  });