React15 - 如何在React 15中实现自定义的事件订阅与发送(例如组件间通信)

在React 15中实现组件间的自定义事件通信,通常采用"发布-订阅"(Pub-Sub)模式。这种模式能让你摆脱繁琐的props层层传递,让任意两个组件直接"对话"。根据你的项目需求,主要有三种实现方式,我按推荐程度逐一介绍。

🎯 方案一:使用专门的库(如mitt)

这是最推荐的方式,特别适合中等以上复杂度的项目。专门的库通常体积小、API友好,并且已经帮你处理好了细节。

  1. 安装:在项目中安装mitt库。

    npm install mitt
    # 或者
    yarn add mitt
    
  2. 创建事件总线:新建一个文件(如 eventBus.js),创建并导出一个mitt实例,它就成为了我们全局的"通信中心"。

    import mitt from "mitt";
    const emitter = mitt();
    export default emitter;
    
  3. 在组件中"订阅"事件(接收方):在需要接收消息的组件中,通常在componentDidMount(类组件)或useEffect(函数组件)中监听事件。关键:在componentWillUnmount中移除监听,防止内存泄漏。

    // 接收方组件
    import React, { useEffect } from "react";
    import emitter from "./eventBus";
    
    const ReceiverComponent = () => {
      useEffect(() => {
        // 定义处理函数
        const handleMessage = (data) => {
          console.log("收到消息:", data);
          // 更新组件状态或执行其他操作
        };
    
        // 订阅 'message' 事件
        emitter.on("message", handleMessage);
    
        // 组件卸载时取消订阅
        return () => {
          emitter.off("message", handleMessage);
        };
      }, []);
    
      return <div>等待消息...</div>;
    };
    
  4. 在组件中"发布"事件(发送方):在需要发送消息的组件中,通过调用emit方法触发事件,并传递数据。

    // 发送方组件
    import React from "react";
    import emitter from "./eventBus";
    
    const SenderComponent = () => {
      const sendMessage = () => {
        // 发布 'message' 事件,并携带数据
        emitter.emit("message", "你好,接收方!");
      };
    
      return <button onClick={sendMessage}>发送消息</button>;
    };
    

🧱 方案二:手写一个简单的Event Bus

如果你希望不引入任何外部依赖,手写一个简单的Event Bus是很好的轻量级方案。

  1. 创建Event Bus工具:新建eventBus.js,实现一个简单的发布-订阅类。

    // eventBus.js
    class EventBus {
      constructor() {
        this.events = {}; // 用于存储事件名和对应的回调函数列表
      }
    
      // 订阅事件
      subscribe(eventName, callback) {
        if (!this.events[eventName]) {
          this.events[eventName] = [];
        }
        this.events[eventName].push(callback);
    
        // 返回一个取消订阅的函数
        return () => {
          this.events[eventName] = this.events[eventName].filter(
            (fn) => fn !== callback,
          );
        };
      }
    
      // 发布事件
      publish(eventName, data) {
        if (this.events[eventName]) {
          this.events[eventName].forEach((callback) => callback(data));
        }
      }
    }
    
    const eventBus = new EventBus();
    export default eventBus;
    
  2. 在组件中使用:使用方式与mitt类似,通过eventBus.subscribe监听,eventBus.publish触发。

    // 接收方
    import React, { useEffect } from "react";
    import eventBus from "./eventBus";
    
    function Receiver() {
      useEffect(() => {
        const unsubscribe = eventBus.subscribe("userLogin", (userData) => {
          console.log("用户登录:", userData);
        });
        return unsubscribe; // 清理订阅
      }, []);
      // ...
    }
    
    // 发送方
    function Sender() {
      const login = () => {
        eventBus.publish("userLogin", { name: "Alice", id: 123 });
      };
      // ...
    }
    

🌐 方案三:利用浏览器原生API (EventTarget)

这个方案利用了浏览器内置的EventTarget API和CustomEvent,无需额外安装库,也无需手写复杂逻辑,是一种现代、轻量且原生的方式。

  1. 创建事件总线:直接创建一个EventTarget实例作为中央总线。

    // eventBus.js
    const eventTarget = new EventTarget();
    export default eventTarget;
    
  2. 订阅事件:使用addEventListener监听自定义事件,并通过event.detail获取传递的数据。

    // 接收方组件
    import React, { useEffect } from "react";
    import eventTarget from "./eventBus";
    
    const Receiver = () => {
      useEffect(() => {
        const handleMessage = (event) => {
          console.log("收到:", event.detail); // 数据在 detail 属性中
        };
    
        eventTarget.addEventListener("message", handleMessage);
    
        return () => {
          eventTarget.removeEventListener("message", handleMessage);
        };
      }, []);
      // ...
    };
    
  3. 发布事件:使用dispatchEvent方法,并创建一个CustomEvent对象,通过其detail属性携带数据。

    // 发送方组件
    import React from "react";
    import eventTarget from "./eventBus";
    
    const Sender = () => {
      const sendMessage = () => {
        const event = new CustomEvent("message", {
          detail: "Hello from EventTarget!",
        });
        eventTarget.dispatchEvent(event);
      };
      return <button onClick={sendMessage}>发送</button>;
    };
    

💎 总结与建议

以上三种方式都能实现你需要的自定义事件通信。它们如何选择,可以参考这个思路:

方案 优点 缺点 适用场景
mitt库 功能完善,API友好,体积超小(200字节),社区成熟 需要额外安装依赖 推荐用于大多数项目,特别是需要可靠、简洁方案时
手写Event Bus 无依赖,完全掌控,理解原理 需要自己处理边界情况,代码稍多 学习目的,或项目有严格的依赖限制
原生EventTarget 无依赖,利用浏览器内置API,现代、简洁 数据需要放在detail属性里,写法稍显啰嗦 喜欢原生API,不想引入任何库或手写维护代码的场景

另外,无论选择哪种方式,一个重要的原则是:在组件卸载时务必取消订阅,这是避免内存泄漏和诡异bug的关键。

posted @ 2026-03-23 00:10  箫笛  阅读(1)  评论(0)    收藏  举报