当前位置: 首页 > news >正文

手写微前端microApp-数据通信

手写微前端micro-app(4)-数据通信

首先我们要实现两个重要的功能:

1、发布订阅

2、自定义元素支持传递对象数据
我们可以使用很简单的代码来模拟这个过程

class EventCenter{eventList = new Map(); //绑定数据与函数//监听事件on(name,f){//先从Map中取出对应的事件列表let eventInfo = this.eventList.get(name);if(!eventInfo){//如果没有,就创建一个eventInfo = {data:{},//存放数据callback:new Set() //存放函数,可能不止一个,并且不重复}//放入Map缓存中this.eventList.set(name,eventInfo);}//记录绑定函数eventInfo.callback.add(f);}//解除绑定off(name,f){let eventInfo = this.eventList.get(name);if(eventInfo && eventInfo.callback.has(f)){eventInfo.callback.delete(f);}}//发送事件dispatch(name,data){const eventInfo = this.eventList.get(name);//只有传入的数据和原来的数据不一致的时候才会触发if(eventInfo && eventInfo.data !== data){eventInfo.data = data;eventInfo.callback.forEach(f => {f(data);});}}
}const eventCenter = new EventCenter();
eventCenter.on('testA',data => {console.log('testA:',data);
});
eventCenter.on('testB',data => {console.log('testB--- :',data);
});
eventCenter.on('testC',data => {console.log('testC:',data);
});eventCenter.dispatch('testA','hello world');

我们可以通过创建两个简单的类,来模拟一下数据通过发布订阅事件中心传递的过程

const eventCenter = new EventCenter()// 主应用处理类class BaseApp{setData(name,data){eventCenter.dispatch(name,data);}}// 子应用处理类class SubApp{constructor (appName) {this.appName = appName}addDataListener(cb){eventCenter.on(this.appName,cb);}dispatch (data) {// 子应用以自定义事件的形式发送数据const event = new CustomEvent('datachange', {detail: {data,}})//具体执行时,可以传递对应的dom用来触发事件window.dispatchEvent(event)}}// window通过自定义事件,监听子应用的数据变化window.addEventListener('datachange',e=>{console.log('datachange:',e.detail.data);})const subApp = new SubApp('subApp');// 子应用发布数据subApp.dispatch('---hello world---');// 子应用监听数据变化subApp.addDataListener(data=>{console.log('subApp:',data);})// 主应用通过子应用的名字发布数据给subAppconst baseApp = new BaseApp();baseApp.setData('subApp','hello world');

1、创建data.js

需要基座应用发送数据到具体的子应用,而子应用也需要发送数据到基座应用。不考虑子应用之间的互相通信,实现这个效果,可以通过基座应用进行中转

// 发布订阅系统
class EventCenter {// 缓存数据和绑定函数eventList = new Map()/*** 绑定监听函数* @param name 事件名称* @param f 绑定函数*/on (name, f) {let eventInfo = this.eventList.get(name)// 如果没有缓存,则初始化if (!eventInfo) {eventInfo = {data: {},callbacks: new Set(),}// 放入缓存this.eventList.set(name, eventInfo)}// 记录绑定函数eventInfo.callbacks.add(f)}// 解除绑定off (name, f) {const eventInfo = this.eventList.get(name)// eventInfo存在且f为函数则卸载指定函数if (eventInfo && typeof f === 'function') {eventInfo.callbacks.delete(f)}}// 发送数据dispatch (name, data) {const eventInfo = this.eventList.get(name)// 当数据不相等时才更新if (eventInfo && eventInfo.data !== data) {eventInfo.data = data// 遍历执行所有绑定函数for (const f of eventInfo.callbacks) {f(data)}}}
}// 创建发布订阅对象
const eventCenter = new EventCenter()

由于基座应用和子应用通信方式不一样,我们可以分开定义.

基座应用很简单,只需要做发布和清除即可

// 基座应用的数据通信
export class EventCenterForBaseApp {/*** 向指定子应用发送数据* @param appName 子应用名称* @param data 对象数据*/setData (appName, data) {eventCenter.dispatch(fappName, data)}
}

子应用的数据通讯,子应用需要监听事件,并且子应用需要绑定自定义事件,将其绑定到<micro-app>标签上,方便基座应用直接通过标签事件获取数据(这个过程其实和vue子组件通过$emit传递数据到父组件,父组件通过v-on获取数据的过程是一致的)

import { appInstanceMap } from './app'// 子应用的数据通信
export class EventCenterForMicroApp {constructor (appName) {this.appName = appName}/*** 监听基座应用发送的数据* @param cb 回调函数*/addDataListener (cb) {eventCenter.on(this.appName, cb)}/*** 向基座应用发送数据* @param data 对象数据*/dispatch (data) {//通过保存在缓存中的名字来获取具体的子应用标签对象,也就是<micro-app>标签const app = appInstanceMap.get(this.appName)if (app.container) {// 子应用以自定义事件的形式发送数据const event = new CustomEvent('datachange', {detail: {data,}})//将事件绑定到<micro-app>标签app.container.dispatchEvent(event)}}
}

2、使用

在沙箱中创建子应用的通信对象,并在沙箱关闭时清空所有绑定的事件

由于子应用通信需要用到应用名称,因此,沙箱的constructor构造器,需要参数,注意在调用的时候需要传递appName

+ import { EventCenterForMicroApp } from './data'export default class SandBox {
+  constructor (appName) {
+    // 创建数据通信对象
+    this.microWindow.microApp = new EventCenterForMicroApp(appName)...}stop () {if (this.active) {...// 清空所有绑定函数
+    	this.microWindow.microApp.clearDataListener()}}
}

这里修改SandBox的构造器,因此,别忘记在调用的时候,修改SandBox构造器传参

// /src/micro-ce/app.js
export default class CreateApp {constructor({ name, url, container }) {......
-		this.SandBox = new SandBox()
+   this.SandBox = new SandBox(name) // 创建沙箱}......
}

在index.js中调用基座应用的数据处理

import { defineElement } from './element'
+ import { EventCenterForBaseApp } from './data'
+ const BaseAppData = new EventCenterForBaseApp()const SimpleMicroApp = {start () {defineElement()}
}

3、自定义元素支持传递对象数据

自定义元素无法支持对象类型的属性,只能传递字符串,例如<micro-app data={x: 1}></micro-app> 会转换为 <micro-app data='[object Object]'></micro-app>,想要以组件化形式进行数据通信必须让元素支持对象类型属性,为此需要重写micro-app原型链上setAttribute方法处理对象类型属性。

// 记录原生方法
const rawSetAttribute = Element.prototype.setAttribute// 重写setAttribute
Element.prototype.setAttribute = function setAttribute(key, value) {// 目标为micro-app标签且属性名称为data时进行处理if (/^micro-app/i.test(this.tagName) && key === 'data') {if (toString.call(value) === '[object Object]') {const cloneValue = {}//Object.getOwnPropertyNames 返回对象的全部属性名Object.getOwnPropertyNames(value).forEach((propertyKey) => {// 过滤vue框架注入的数据// 因为获取的数据中会有vue框架注入的数据,// 这些数据都是以__开头的,所以我们在这里过滤掉,比如 // __ob__:Observer {value: {…}, shallow: false, mock: false, dep: Dep, vmCount: 0}if (!(typeof propertyKey === 'string' && propertyKey.indexOf('__') === 0)) {cloneValue[propertyKey] = value[propertyKey]}})// 发送数据BaseAppData.setData(this.getAttribute('name'), cloneValue)}} else {rawSetAttribute.call(this, key, value)}
}

界面调用

主应用:

<micro-app :data="data" @datachange="handleSubData"  name="app" url="http://localhost:3001" destroy
>
</micro-app>export default {data () {return {data: {name: 'micro-app',type:'微前端'}}},mounted() { setTimeout(() => {this.data = {name: '---来自基座应用的数据---',type:'微前端'}}, 2000);},methods: {handleSubData(e) { console.log('基座接收数据:', e.detail.data);}}
}

子应用:

window.microApp.addDataListener((data) => { console.log('子应用接收数据:', data);
})setTimeout(() => { window.microApp.dispatch({ name: '来自react子应用的数据' });
},3000)

相关文章:

  • 【Go语言】面向对象编程(二):通过组合实现类的继承和方法重写
  • 【深度学习】数竹签演示软件系统
  • vue3 基于el-tree增加、删除节点(非TypeScript 写法)
  • 深入解读Prometheus Adapter:云原生监控的核心组件
  • 生成式人工智能 - stable diffusion web-ui安装教程
  • Struts2 系列漏洞 - S2-003、S2-005
  • 蓝桥杯物联网竞赛_STM32L071KBU6_第十五届蓝桥杯物联网竞赛国赛代码解析
  • Shell脚本
  • Web前端开发 - 5 - JavaScript基础
  • 滴滴出行 大数据研发实习生【继任】
  • Direct local .aar file dependencies are not supported when building an AAR.
  • 算法之分治
  • java 中for、while循环
  • Apache Hadoop的核心组成及其架构
  • 气膜建筑在体育和娱乐行业的多样化应用—轻空间
  • 分享的文章《人生如棋》
  • CoolViewPager:即刻刷新,自定义边缘效果颜色,双向自动循环,内置垂直切换效果,想要的都在这里...
  • GitUp, 你不可错过的秀外慧中的git工具
  • HashMap剖析之内部结构
  • Javascript编码规范
  • js学习笔记
  • Magento 1.x 中文订单打印乱码
  • PhantomJS 安装
  • 对话 CTO〡听神策数据 CTO 曹犟描绘数据分析行业的无限可能
  • 缓存与缓冲
  • 机器学习学习笔记一
  • 每个JavaScript开发人员应阅读的书【1】 - JavaScript: The Good Parts
  • 前端存储 - localStorage
  • 强力优化Rancher k8s中国区的使用体验
  • 云大使推广中的常见热门问题
  • 《码出高效》学习笔记与书中错误记录
  • Semaphore
  • 阿里云移动端播放器高级功能介绍
  • ​七周四次课(5月9日)iptables filter表案例、iptables nat表应用
  • #Linux(权限管理)
  • (12)Hive调优——count distinct去重优化
  • (C语言)球球大作战
  • (NO.00004)iOS实现打砖块游戏(九):游戏中小球与反弹棒的碰撞
  • (SpringBoot)第二章:Spring创建和使用
  • (vue)el-cascader级联选择器按勾选的顺序传值,摆脱层级约束
  • (八)Flask之app.route装饰器函数的参数
  • (二十一)devops持续集成开发——使用jenkins的Docker Pipeline插件完成docker项目的pipeline流水线发布
  • (附源码)springboot建达集团公司平台 毕业设计 141538
  • (蓝桥杯每日一题)love
  • (六) ES6 新特性 —— 迭代器(iterator)
  • (十三)Flink SQL
  • (一)模式识别——基于SVM的道路分割实验(附资源)
  • .NET Core跨平台微服务学习资源
  • .NET 项目中发送电子邮件异步处理和错误机制的解决方案
  • .NET业务框架的构建
  • .net专家(张羿专栏)
  • @DateTimeFormat 和 @JsonFormat 注解详解
  • [Android] Upload package to device fails #2720
  • [Angular] 笔记 7:模块
  • [C#]获取指定文件夹下的所有文件名(递归)