手写微前端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);
  })

  // 主应用通过子应用的名字发布数据给subApp
  const 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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值