js自定义事件,在react和vue中实现你自己的事件总线✨

1,288 阅读3分钟

事件

事件是javascript与浏览器交互的主要途径。事件是一种叫做观察者的设计模式,这是一种创建松散耦合代码的技术。对象可以发布事件,用来表示在该对象生命周期中某个有趣的时候到了。然后其他对象可以观察该对象,等待这些有趣的时刻到来并通过运行代码并响应。

观察者模式

观察者模式有两类对象组成,主体观察者。主体负责发布事件,同时观察者通过订阅这些事件来观察主体。该模式的一个关键概念是主体并不知道观察者的任何事情,也就是说它可以独自存在并正常运作即时观察者不存在。从另一方面来说,观察者知道主体并能注册事件的回调函数。当涉及DOM时,DOM便是主体,你的事件处理代码便是观察者。

自定义事件

概念

我们知道了事件的基本概念,便得知了自定义事件要符合的条件:概念是创建一个管理事件的对象,让其他对象监听那些事件

代码实现

   EventBus.js
   function EventTarget() {
      // handlers用于储存事件处理程序
      this.handlers = {};
    }

EventTarget.prototype = {
  constructor: EventTarget,

  /**
   * @name: addHandler用于注册给定事件的事件处理程序
   * @msg: 当调用该方法发的时候,会进行一次检查,看看handlers属性中是否已经存在一个该事件类型的数组\
   *       如果没有,则会创建一个新的数组,然后使用push将该处理程序添加到数组的末尾
   * @param {*} type 事件类型
   * @param {*} handler 处理该事件的函数
   */
  addHandler: function (type, handler) {
    if (typeof this.handlers[type] === "undefined") {
      this.handlers[type] = [];
    }
    this.handlers[type].push(handler);
  },

  /**
   * @name: fire 用于触发一个事件
   * @msg: 先给event对象设置一个target属性,如果它尚未指定的话
   *       然后查找对应事件类的一组处理程序,调用各个函数,并给出event对象
   * @param {*} event 至少包含type的属性对象
   */
  fire: function (event) {
    if (!event.target) {
      event.target = this;
    }
    if (this.handlers[event.type] instanceof Array) {
      var handlers = this.handlers[event.type];
      for (let i = 0, len = handlers.length; i < len; i++) {
        handlers[i](event)
      }
    }
  },

  /**
   * @name: removeHandler 是addHandler方法的辅助,它们接受的参数一样
   * @msg:搜索事件处理程序的数组找到要删除的处理程序的位置,然后删除
   * @param {*} type 事件类型
   * @param {*} handler 处理事件的程序
   */
  removeHandler: function (type, handler) {
    if (this.handlers[type] instanceof Array) {
      var handlers = this.handlers[type];
      for (var i = 0, len = handlers.length; i < len; i++) {
        if (handlers[i] === handler)
          break;
      }
      handlers.splice(i, 1)
    }
  }
}

export default new EventTarget();
  • 以上文件中我们实现了EventTarget,对应方法addHandler注册/订阅事件,fire触发事件,removeHandler删除事件

React中实现

文件目录

|-- App.js # 根组件
|-- pages
|-----|-- login.js # 登录页面
|-- EventBus
|-----|-- index.js # 事件总线文件
...
其他文件不做赘述
...

测试方法

为了方便贴代码,我在login.js页面中写了两个测试组件,组件传值不会通过Login中转,而是通过组件A订阅事件,组件B触发之间的方式进行传值

login.js

import React from 'react';
import EventBus from "../EventBus";

export default () => {
  return (
    <React.Fragment>
      <div>
        登录模块
        <Com1 />
        <Com2 />
      </div>

    </React.Fragment>
  )
}

function Com1() {
  // 触发事件
  function emit() {
    EventBus.fire({
      type: "login",
      data: { code: "200", massage: "登录成功" }
    })
  }
  return (
    <React.Fragment>
      <div>COM1</div>
      <button onClick={emit}>com1触发</button>
    </React.Fragment>
  )
}

function Com2() {
  React.useEffect(() => {
    // 注册/订阅事件
    EventBus.addHandler("login", (event) => {
      console.log(event.data, "[COM2模块接受]")
    })
    return () => EventBus.removeHandler('login', null)
  }, []);
  return (
    <React.Fragment>
      <div>
        COM2
      </div>
    </React.Fragment>
  )
}
  • 如上,我在Com2组件挂载后订阅了login事件,Com1中通过点击事件触发了login事件,我们来看下效果截图吧。

image.png

  • 我们在React中实现了自定义事件总线方式传值

Vue中实现

文件目录

|-- App.vue # 根组件
|-- component
|-----|-- Sidebar.vue #侧边栏组件
|-----|-- Header.vue #页面头部组件
|-- EventBus
|-----|-- index.js # 事件总线文件
...
其他文件不做赘述
...

测试方法

image.png 我将通过点击Header组件中的按钮A对SideBar组件的收起/展开功能来测试自定义事件在Vue中的实现

// 只贴相关代码
Sidebar.vue
      created() {
           // 组件实例创建之后注册/订阅 collapse事件
          EventBus.addHandler('collapse', (event) => {
              this.collapse = event.data;
          });
      }
 
Header.vue
           // 侧边栏折叠 按钮A的点击事件
          collapseChage() {
              this.collapse = !this.collapse;
              EventBus.fire({ type: 'collapse', data: this.collapse });
          },
  • 如上,我在SideBar实例创建后注册订阅collapse事件,在组件Header组件中点按钮A点击事件中触发了collapse事件

image.png

  • 我们在Vue中实现了自定义事件总线方式传值

概念性文字参考了《JavaScript高级程序设计》

以上