首页 > 其他分享 >04|发布-订阅模型

04|发布-订阅模型

时间:2022-12-28 20:12:48浏览次数:44  
标签:订阅 04 模型 eventMap handler params 事件 type 监听

发布-订阅模型 API 设计思路

发布-订阅模式中有两个关键的动作:事件的监听(订阅)和事件的触发(发布),这两个动作自然而然地对应着两个基本的 API 方法。

  • on():负责注册事件的监听器,指定事件触发时的回调函数。

  • emit():负责触发事件,可以通过传参使其在触发的时候携带数据 。

最后,只进不出总是不太合理的,我们还要考虑一个 off() 方法,必要的时候用它来删除用不到的监听器:

  • off():负责监听器的删除。

发布-订阅模型编码实现

在写代码之前,先要捋清楚思路。这里我把“实现 EventEmitter”这个大问题,拆解为 3 个具体的小问题,下面我们逐个来解决。

问题一:事件和监听函数的对应关系如何处理?

提到“对应关系”,应该联想到的是“映射”。在 JavaScript 中,处理“映射”我们大部分情况下都是用对象来做的。所以说在全局我们需要设置一个对象,来存储事件和监听函数之间的关系:

constructor() {
  // eventMap 用来存储事件和监听函数之间的关系
  this.eventMap= {}
}

问题二:如何实现订阅?

所谓“订阅”,也就是注册事件监听函数的过程。这是一个“写”操作,具体来说就是把事件和对应的监听函数写入到 eventMap 里面去:

// type 这里就代表事件的名称
on(type, handler) {
  // hanlder 必须是一个函数,如果不是直接报错
  if(!(handler instanceof Function)) {
    throw new Error("哥 你错了 请传一个函数")
  }
  // 判断 type 事件对应的队列是否存在
  if(!this.eventMap[type]) {
   // 若不存在,新建该队列
    this.eventMap[type] = []
  }
  // 若存在,直接往队列里推入 handler
  this.eventMap[type].push(handler)
}

问题三:如何实现发布?

订阅操作是一个“写”操作,相应的,发布操作就是一个“读”操作。发布的本质是触发安装在某个事件上的监听函数,我们需要做的就是找到这个事件对应的监听函数队列,将队列中的 handler 依次执行出队:

// 别忘了我们前面说过触发时是可以携带数据的,params 就是数据的载体
emit(type, params) {
  // 假设该事件是有订阅的(对应的事件队列存在)
  if(this.eventMap[type]) {
    // 将事件队列里的 handler 依次执行出队
    this.eventMap[type].forEach((handler, index)=> {
      // 注意别忘了读取 params
      handler(params)
    })
  }
}

到这里,最最关键的 on 方法和 emit 方法就实现完毕了。最后我们补充一个 off 方法:

off(type, handler) {
  if(this.eventMap[type]) {
    this.eventMap[type].splice(this.eventMap[type].indexOf(handler)>>>0,1)
  }
}

一个核心功能完备的 EventEmitter 如下:

class myEventEmitter {
  constructor() {
    // eventMap 用来存储事件和监听函数之间的关系
    this.eventMap = {};
  }
  // type 这里就代表事件的名称
  on(type, handler) {
    // hanlder 必须是一个函数,如果不是直接报错
    if (!(handler instanceof Function)) {
      throw new Error("哥 你错了 请传一个函数");
    }
    // 判断 type 事件对应的队列是否存在
    if (!this.eventMap[type]) {
      // 若不存在,新建该队列
      this.eventMap[type] = [];
    }
    // 若存在,直接往队列里推入 handler
    this.eventMap[type].push(handler);
  }
  // 别忘了我们前面说过触发时是可以携带数据的,params 就是数据的载体
  emit(type, params) {
    // 假设该事件是有订阅的(对应的事件队列存在)
    if (this.eventMap[type]) {
      // 将事件队列里的 handler 依次执行出队
      this.eventMap[type].forEach((handler, index) => {
        // 注意别忘了读取 params
        handler(params);
      });
    }
  }
  off(type, handler) {
    if (this.eventMap[type]) {
      this.eventMap[type].splice(this.eventMap[type].indexOf(handler) >>> 0, 1);
    }
  }
}

测试

简单的测试

下面我们对 myEventEmitter 进行一个简单的测试,创建一个 myEvent 对象作为 myEventEmitter 的实例,然后针对名为 “test” 的事件进行监听和触发:

// 实例化 myEventEmitter
const myEvent = new myEventEmitter();
// 编写一个简单的 handler
const testHandler = function (params) {
  console.log(`test事件被触发了,testHandler 接收到的入参是${params}`);
};
// 监听 test 事件
myEvent.on("test", testHandler);
// 在触发 test 事件的同时,传入希望 testHandler 感知的参数
myEvent.emit("test", "newState");

React任意的两个组件 A 和 B,进行通信

,以数据从 A 流向 B 为例:

在 B 中编写一个handler(记得将这个 handler 的 this 绑到 B 身上),在这个 handler 中进行以 B 为上下文的 this.setState 操作,然后将这个 handler 作为监听器与某个事件关联起来。比如这样:

// 注意这个 myEvent 是提前实例化并挂载到全局的,此处不再重复示范实例化过程
const globalEvent = window.myEvent
class B extends React.Component {
  // 这里省略掉其他业务逻辑
  state = {
    newParams: ""
  };
  handler = (params) => {
    this.setState({
      newParams: params
    });
  };
  bindHandler = () => {
    globalEvent.on("someEvent", this.handler);
  };
  render() {
    return (
      <div>
        <button onClick={this.bindHandler}>点我监听A的动作</button>
        <div>A传入的内容是[{this.state.newParams}]</div>
      </div>
    );
  }
}

接下来在 A 组件中,只需要直接触发对应的事件,然后将希望携带给 B 的数据作为入参传递给 emit 方法即可。代码如下:

class A extends React.Component {
  // 这里省略掉其他业务逻辑
  state = {
    infoToB: "哈哈哈哈我来自A"
  };
  reportToB = () => {
    // 这里的 infoToB 表示 A 自身状态中需要让 B 感知的那部分数据
    globalEvent.emit("someEvent", this.state.infoToB);
  };
  render() {
    return <button onClick={this.reportToB}>点我把state传递给B</button>;
  }
}

标签:订阅,04,模型,eventMap,handler,params,事件,type,监听
From: https://www.cnblogs.com/superlizhao/p/17011180.html

相关文章

  • 04-前端技术_CSS与CSS3美化页面
    目录​​二,CSS与CSS3美化页面​​​​1,CSS简介​​​​1.1 什么是CSS?​​​​1.2 样式层叠次序​​​​2,CSS基础语法​​​​2.1介绍​​​​2.2注释​​​​3,CSS使用......
  • 3d渲染的shading模型
    flatshadingFLAT指的是把三角形的三个顶点的发现向量都指定为该三角形所在平面的法线向量,这种方式绘制出来的效果能清楚地看到模型上的三角面片。它是块渲染,而不是插值渲......
  • 【杂谈】如何从数据准备,模型设计与调优,训练到部署完成整个深度学习算法流程...
    文/编辑|言有三对于一个深度学习算法工程师来说,拥有丰富的项目经历当然是重要的,但是拥有完成整个从数据准备到模型上线的能力更加重要。这意味着可以独立承担项目,也是......
  • Ubuntu 22.04 搭建编译Android源码环境
    环境准备操作系统编译Android源码需要一个区分文件大小写的系统环境,一般使用Ubuntu,可以采用单独安装Ubuntu、虚拟机等形式。这里使用VMware虚拟机的形式,具体安装过程省略......
  • 04Servlet-5. Servlet的类试图详解
    1、什么是Servlet?JavaServlet是运行在Web服务器或应用服务器上的程序,它是作为来自Web浏览器或其他HTTP客户端的请求和HTTP服务器上的数据库或应用程序之间的中......
  • 二、PO模型之注册界面组装成完整的自动化case-5
    base层:基础层。获取配置文件中的元素值。page层:页面层。页面就是元素。将读取页面信息放在page中,page给handle提供页面元素。handle层:定义方法,调用page层。即处理page。......
  • 评价类模型 - 灰色关联模型
    目的:考察某个指标与其它指标的关联程度大小解决:将某个指标设为参考数列,(当然所有指标都需要经过标准化去量纲,一般采用初值化(都除以第一个元素))利用公式求出每个人对应的每......
  • LOJ 6041 「雅礼集训 2017 Day7」事情的相似度 题解 (SAM+启发式合并)
    题目链接首先很容易想到的是对反串求SA和LCP,然后询问就是求起点在某个区间内的所有后缀两两LCP的最大值,可以用莫队解决,时间复杂度\(O(n\sqrtnlogn)\),应该是过不了的。......
  • JVM 内存模型 Stack Heap 文章选摘
    JVM内存模型Java虚拟机(JavaVirtualMachine=JVM)的内存空间分为五个部分,分别是:程序计数器Java虚拟机栈本地方法栈堆方法区。下面对这五个区域展开深入的介绍。程序......
  • Ubuntu 16.04 64位安装kafka
    在Ubuntu下安装完成kafka需要以下主要的2个步骤:1.安装jdk1.8以上版本。2.安装zookeeper.开始以上操作步骤:3.安装jdk.先下载jdk,url:https://www.oracle.com/java/t......